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|)$,反正跑得挺快就是了。
1 /************************************************************** 2 Problem: 2555 3 User: hzoier 4 Language: C++ 5 Result: Accepted 6 Time:9748 ms 7 Memory:47112 kb 8 ****************************************************************/ 9 #include<cstdio> 10 #include<cstring> 11 #include<algorithm> 12 #define isroot(x) ((x)->p==null||((x)->p->ch[0]!=(x)&&((x)->p->ch[1]!=(x)))) 13 #define dir(x) ((x)==(x)->p->ch[1]) 14 using namespace std; 15 const int maxn=1200010,maxm=3000010; 16 struct node{ 17 int key,lazy; 18 node *ch[2],*p; 19 node(int d=0):key(d),lazy(0){} 20 inline void pushdown(){ 21 if(!lazy)return; 22 key+=lazy; 23 ch[0]->lazy+=lazy; 24 ch[1]->lazy+=lazy; 25 lazy=0; 26 } 27 }null[maxn]; 28 void decode(char*,int,int); 29 void expand(int); 30 int travel(const char*); 31 node *access(node*); 32 void link(node*,node*); 33 void cut(node*); 34 void splay(node*); 35 void rot(node*,int); 36 int root,last,cnt=0,val[maxn]={0},par[maxn]={0},go[maxn][2]={{0}}; 37 char s[maxm],c[maxn]; 38 int n,q,mask=0,ans; 39 int main(){ 40 null->ch[0]=null->ch[1]=null->p=null; 41 root=last=++cnt; 42 null[root].ch[0]=null[root].ch[1]=null[root].p=null; 43 null[root].key=1; 44 scanf("%d%s",&q,s); 45 n=strlen(s); 46 for(int i=0;i<n;i++)expand(s[i]-'A'); 47 while(q--){ 48 scanf("%s%s",c,s); 49 decode(s,n=strlen(s),mask); 50 if(!strcmp(c,"ADD"))for(int i=0;i<n;i++)expand(s[i]-'A'); 51 else{ 52 printf("%d\n",ans=travel(s)); 53 mask^=ans; 54 } 55 } 56 return 0; 57 } 58 void decode(char *s,int n,int mask){ 59 for(int i=0;i<n;i++){ 60 mask=(mask*131+i)%n; 61 swap(s[i],s[mask]); 62 } 63 } 64 void expand(int c){ 65 int p=last,np=++cnt; 66 val[np]=val[p]+1; 67 null[np].ch[0]=null[np].ch[1]=null[np].p=null; 68 null[np].key=1; 69 while(p&&!go[p][c]){ 70 go[p][c]=np; 71 p=par[p]; 72 } 73 if(!p){ 74 par[np]=root; 75 link(null+np,null+root); 76 } 77 else{ 78 int q=go[p][c]; 79 if(val[q]==val[p]+1){ 80 par[np]=q; 81 link(null+np,null+q); 82 } 83 else{ 84 int nq=++cnt; 85 val[nq]=val[p]+1; 86 memcpy(go[nq],go[q],sizeof(go[q])); 87 null[nq].ch[0]=null[nq].ch[1]=null[nq].p=null; 88 par[nq]=par[q]; 89 link(null+nq,null+par[q]); 90 par[np]=par[q]=nq; 91 link(null+np,null+nq); 92 cut(null+q); 93 link(null+q,null+nq); 94 while(p&&go[p][c]==q){ 95 go[p][c]=nq; 96 p=par[p]; 97 } 98 } 99 } 100 last=np; 101 } 102 int travel(const char *c){ 103 int x=root; 104 while(*c)x=go[x][*c++-'A']; 105 if(!x)return 0; 106 splay(null+x); 107 return null[x].key; 108 } 109 node *access(node *x){ 110 node *y=null; 111 while(x!=null){ 112 splay(x); 113 x->ch[1]=y; 114 y=x; 115 x=x->p; 116 } 117 return y; 118 } 119 void link(node *x,node *y){ 120 splay(x); 121 x->p=y; 122 access(y)->lazy+=x->key; 123 } 124 void cut(node *x){ 125 access(x); 126 splay(x); 127 x->ch[0]->lazy-=x->key; 128 x->ch[0]->p=null; 129 x->ch[0]=null; 130 } 131 void splay(node *x){ 132 x->pushdown(); 133 while(!isroot(x)){ 134 if(!isroot(x->p))x->p->p->pushdown(); 135 x->p->pushdown(); 136 x->pushdown(); 137 if(isroot(x->p)){ 138 rot(x->p,dir(x)^1); 139 break; 140 } 141 if(dir(x)==dir(x->p))rot(x->p->p,dir(x->p)^1); 142 else rot(x->p,dir(x)^1); 143 rot(x->p,dir(x)^1); 144 } 145 } 146 void rot(node *x,int d){ 147 node *y=x->ch[d^1]; 148 if((x->ch[d^1]=y->ch[d])!=null)y->ch[d]->p=x; 149 y->p=x->p; 150 if(!isroot(x))x->p->ch[dir(x)]=y; 151 (y->ch[d]=x)->p=y; 152 } 153 /* 154 操作都是向后添加字符,因此考虑后缀自动机。 155 又因为需要动态维护子树大小,因此用LCT维护。 156 */
其实还有平衡树维护dfs序列的做法,然而以前没写过没敢写……
还有两个个脑洞级别的做法(虽然分块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写错了……这人没救了