bzoj2555 SubString
这**什么破题……
看题的时候突然发现:咦这题我怎么见过……后来想起来是sxysxy说给我的一道后缀自动机+LCT的破题,然后我偏不信这个邪,码了个分块hash试图水过去,然后发现不是TLE就是MLE……没办法,只能乖乖写正解了……
加入操作只会在原串末尾加一个字符串,不难想到维护当前串的后缀自动机,加入的时候直接逐个加入字符即可。询问的时候首先在后缀自动机上进行转移,转移到的那个点的right大小即为答案。而一个点的right大小就等于parent树上子树中不是因复制而产生的节点个数(似乎表达有点问题,凑合看吧……),而parent树的形态会发生变化,因此可以用LCT维护子树大小,实现的时候把这个点的父亲到根的值全部加上/减去这个子树的大小即可。
后缀自动机的更新均摊O(1),LCT单次操作均摊O(logn),总复杂度就是$O(|S|\log |S|+\sum{|T|}+Q\log |S|)$,反正跑得挺快就是了。
/************************************************************** Problem: 2555 User: hzoier Language: C++ Result: Accepted Time:9748 ms Memory:47112 kb ****************************************************************/ #include<cstdio> #include<cstring> #include<algorithm> #define isroot(x) ((x)->p==null||((x)->p->ch[0]!=(x)&&((x)->p->ch[1]!=(x)))) #define dir(x) ((x)==(x)->p->ch[1]) using namespace std; const int maxn=1200010,maxm=3000010; struct node{ int key,lazy; node *ch[2],*p; node(int d=0):key(d),lazy(0){} inline void pushdown(){ if(!lazy)return; key+=lazy; ch[0]->lazy+=lazy; ch[1]->lazy+=lazy; lazy=0; } }null[maxn]; void decode(char*,int,int); void expand(int); int travel(const char*); node *access(node*); void link(node*,node*); void cut(node*); void splay(node*); void rot(node*,int); int root,last,cnt=0,val[maxn]={0},par[maxn]={0},go[maxn][2]={{0}}; char s[maxm],c[maxn]; int n,q,mask=0,ans; int main(){ null->ch[0]=null->ch[1]=null->p=null; root=last=++cnt; null[root].ch[0]=null[root].ch[1]=null[root].p=null; null[root].key=1; scanf("%d%s",&q,s); n=strlen(s); for(int i=0;i<n;i++)expand(s[i]-'A'); while(q--){ scanf("%s%s",c,s); decode(s,n=strlen(s),mask); if(!strcmp(c,"ADD"))for(int i=0;i<n;i++)expand(s[i]-'A'); else{ printf("%d\n",ans=travel(s)); mask^=ans; } } return 0; } void decode(char *s,int n,int mask){ for(int i=0;i<n;i++){ mask=(mask*131+i)%n; swap(s[i],s[mask]); } } void expand(int c){ int p=last,np=++cnt; val[np]=valView Code [p]其实还有平衡树维护dfs序列的做法,然而以前没写过没敢写……+1; null[np].ch[0]=null[np].ch[1]=null[np].p=null; null[np].key=1; while(p&&!go[p][c]){ go[p][c]=np; p=par[p]; } if(!p){ par[np]=root; link(null+np,null+root); } else{ int q=go[p][c]; if(val[q]==val[p]+1){ par[np]=q; link(null+np,null+q); } else{ int nq=++cnt; val[nq]=val[p]+1; memcpy(go[nq],go[q],sizeof(go[q])); null[nq].ch[0]=null[nq].ch[1]=null[nq].p=null; par[nq]=par[q]; link(null+nq,null+par[q]); par[np]=par[q]=nq; link(null+np,null+nq); cut(null+q); link(null+q,null+nq); while(p&&go[p][c]==q){ go[p][c]=nq; p=par[p]; } } } last=np; } int travel(const char *c){ int x=root; while(*c)x=go[x][*c++-'A']; if(!x)return 0; splay(null+x); return null[x].key; } node *access(node *x){ node *y=null; while(x!=null){ splay(x); x->ch[1]=y; y=x; x=x->p; } return y; } void link(node *x,node *y){ splay(x); x->p=y; access(y)->lazy+=x->key; } void cut(node *x){ access(x); splay(x); x->ch[0]->lazy-=x->key; x->ch[0]->p=null; x->ch[0]=null; } void splay(node *x){ x->pushdown(); while(!isroot(x)){ if(!isroot(x->p))x->p->p->pushdown(); x->p->pushdown(); x->pushdown(); if(isroot(x->p)){ rot(x->p,dir(x)^1); break; } if(dir(x)==dir(x->p))rot(x->p->p,dir(x->p)^1); else rot(x->p,dir(x)^1); rot(x->p,dir(x)^1); } } void rot(node *x,int d){ node *y=x->ch[d^1]; if((x->ch[d^1]=y->ch[d])!=null)y->ch[d]->p=x; y->p=x->p; if(!isroot(x))x->p->ch[dir(x)]=y; (y->ch[d]=x)->p=y; } /* 操作都是向后添加字符,因此考虑后缀自动机。 又因为需要动态维护子树大小,因此用LCT维护。 */
还有两个个脑洞级别的做法(虽然分块hash不知为何过不去……):
分块hash,设定一个阈值M,把原串所有长度<=M的子串的hash值扔到hash表里,询问时对于长度>M的串直接暴力KMP或者扫一遍原串的hash值,长度<=M的串扔到hash表里询问一下即可,取$M=\sqrt{\sum{|T|}}$可以达到理论最优复杂度$O(|S|\sqrt{\sum{|T|}} +\sum{|T|}+Q)$,然而用自然溢出hash会被卡,对998244353取模的话就会TLE/MLE……卡我分块,出题人**
既然可以后缀自动机+LCT,总不能不给后缀数组留条活路吧,所以后缀平衡树出场了。把原串反过来,每次添加一个字符就是在反串的前面添加一个字符,用二分+hash或者别的什么办法就可以快速比较两个反串后缀的大小,询问的时候在后缀平衡树上随便搞搞就行了,复杂度应该是$O(\log^2|S|)$,因此如果用二分hash比较后缀大小的话总复杂度应该是$O(|S|\log^2|S|+\sum{|T|}+Q\log^2|S|)$,看上去不错,然而我没写过后缀平衡树,这个做法也就只能留作脑洞了……
调了半天的原因居然又是把splay写错了……这人没救了
- 【bzoj2555】SubString 后缀自动机+LCT
- [BZOJ2555][LCT][后缀自动机]SubString
- bzoj 2555: SubString
- 【BZOJ2555】SubString(后缀自动机,Link-Cut Tree)
- BZOJ2555: SubString
- [bzoj2555]SubString
- BZOJ 2555 Substring(后缀自动机+LCT子树维护)
- [BZOJ2555]SubString(SAM+LCT)
- bzoj 2555 SubString
- 【bzoj2555】 SubString
- 【BZOJ2555】SubString(后缀自动机,Link-Cut Tree)
- [BZOJ]2555 Substring 后缀自动机&LCT
- 【bzoj2555】SubString 后缀自动机+LCT
- 【BZOJ】2555: SubString(后缀自动机)
- BZOJ 2555 SubString LCT 后缀自动机
- BZOJ 2555: SubString
- BZOJ 2555: SubString 动态树+后缀自动机
- [BZOJ2555]SubString(后缀自动机+LCT)
- bzoj 2555: SubString (LCT+后缀自动机)
- [BZOJ2555]SubString(后缀自动机+lct)