首页 > 代码库 > bzoj2555 SubString

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 */
View Code

其实还有平衡树维护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写错了……这人没救了

bzoj2555 SubString