Tjoi2016&Heoi2016 字符串
终于把心头大恨切掉了……后缀自动机大法好,从此抛弃后缀数组哈哈……(说的跟你会写后缀数组似的
好像网上的题解大多都是后缀数组?看了看表示理解不能,那我这份后缀自动机的题解就写详细点好了……
题目跟LCP有关,不难想到后缀树,对反串建后缀自动机之后得到的parent树就是原串的后缀树,之后的操作就在parent树上乱搞就行了。
询问都是询问s[a..b]中的所有子串和s[c..d]的LCP长度的最大值,显然s[a..b]的子串可以直接改成s[a..b]的后缀,那么就有
$ans=\min\{\max_{a\le i\le b}\{\min\{LCP(i,c),b-i+1\}\},d-c+1\}$
记黑点为每个前缀对应的节点,如果没有b-i+1的限制的话,问题就变成了每次询问c与所有编号位于[a,b]的黑点的所有LCA中深度最大的那一个的深度,显然是可以直接上主席树+倍增的,单次询问$O(log^2n)$(@树白黑)。
现在有了b-i+1的限制,可以二分答案,设当前答案为M,任务就变成了判定答案能否$\ge M$。显然只有$b-i+1\ge M$的i合法(即可以使答案$\ge M$),移项得$i\le b-M+1$,再加上$a\le i\le b$的限制即可得出合法的i的范围,再用倍增找到最浅的深度$\ge M$的点(因为这个点要作为深度最小的LCA,或者是这个LCA的祖先),询问一下这个点的子树中是否存在一个编号在合法范围内的黑点即可(因为这个点一定是c的祖先,因此只要子树中有黑点就说明深度最小的LCA不会比它浅),有则说明答案$\ge M$,否则说明答案<M,调整下一次二分即可。
询问子树中是否有黑点可以用主席树,那么每次判定的复杂度就是倍增$O(logn)$+主席树$O(logn)$=$O(logn)$,加上二分答案后单次询问$O(log^2n)$,还是在线算法(虽然大多都是在线不过听说有写离线的……?)
代码里的二分可以保证$M\ge 1$,因此没有判$i\le b$的限制。
1 /************************************************************** 2 Problem: 4556 3 User: hzoier 4 Language: C++ 5 Result: Accepted 6 Time:12996 ms 7 Memory:120180 kb 8 ****************************************************************/ 9 #include<cstdio> 10 #include<cstring> 11 #include<algorithm> 12 #include<vector> 13 using namespace std; 14 const int maxn=200010; 15 void expand(int); 16 void dfs(int); 17 void build(int,int,int&,int); 18 void query(int,int,int,int); 19 vector<int>G[maxn]; 20 int SAM_root,last,SAM_cnt=0,val[maxn]={0},par[maxn]={0},go[maxn][26]={{0}}; 21 int sm[maxn<<5],lc[maxn<<5],rc[maxn<<5]={0},cnt=0,root[maxn]={0}; 22 int f[maxn][20]={{0}},dfn[maxn],finish[maxn],tim=0; 23 char S[maxn]; 24 int n,m,iter[maxn],k=0,a,b,c,d=0,x,r,s,t,tmp; 25 int main(){ 26 SAM_root=last=++SAM_cnt; 27 scanf("%d%d%s",&n,&m,S+1); 28 for(int i=n;i;i--){ 29 expand(S[i]-'a'); 30 iter[i]=last; 31 } 32 for(int i=2;i<=SAM_cnt;i++)G[par[i]].push_back(i); 33 dfs(SAM_root); 34 for(int i=1;i<=n;i++){ 35 x=dfn[iter[i]]; 36 build(1,tim,root[i],root[i-1]); 37 } 38 for(int j=1;j<=k;j++)for(int i=1;i<=tim;i++)f[i][j]=f[f[i][j-1]][j-1]; 39 while(m--){ 40 scanf("%d%d%d%d",&a,&b,&c,&d); 41 int L=1,R=b-a+1; 42 while(L<=R){ 43 int M=(L+R)>>1; 44 x=iter[c]; 45 tmp=0; 46 if(val[x]>=M){ 47 for(int i=k;i>=0;i--)if(val[f[x][i]]>=M)x=f[x][i]; 48 s=dfn[x]; 49 t=finish[x]; 50 if(a<=b-M+1)query(1,tim,root[b-M+1],root[a-1]); 51 } 52 if(tmp)L=M+1; 53 else R=M-1; 54 } 55 printf("%d\n",min(R,d-c+1)); 56 } 57 return 0; 58 } 59 void expand(int c){ 60 int p=last,np=++SAM_cnt; 61 val[np]=val[p]+1; 62 while(p&&!go[p][c]){ 63 go[p][c]=np; 64 p=par[p]; 65 } 66 if(!p)par[np]=SAM_root; 67 else{ 68 int q=go[p][c]; 69 if(val[q]==val[p]+1)par[np]=q; 70 else{ 71 int nq=++SAM_cnt; 72 val[nq]=val[p]+1; 73 memcpy(go[nq],go[q],sizeof(go[q])); 74 par[nq]=par[q]; 75 par[np]=par[q]=nq; 76 while(p&&go[p][c]==q){ 77 go[p][c]=nq; 78 p=par[p]; 79 } 80 } 81 } 82 last=np; 83 } 84 void dfs(int x){ 85 dfn[x]=++tim; 86 d++; 87 while((1<<k)<d)k++; 88 for(int i=0;i<(int)G[x].size();i++){ 89 f[G[x][i]][0]=x; 90 dfs(G[x][i]); 91 } 92 finish[x]=tim; 93 d--; 94 } 95 void build(int l,int r,int &rt,int pr){ 96 sm[rt=++cnt]=sm[pr]+1; 97 if(l==r)return; 98 lc[rt]=lc[pr]; 99 rc[rt]=rc[pr]; 100 int mid=(l+r)>>1; 101 if(x<=mid)build(l,mid,lc[rt],lc[pr]); 102 else build(mid+1,r,rc[rt],rc[pr]); 103 } 104 void query(int l,int r,int rt,int pr){ 105 if(!rt&&!pr)return; 106 if(s<=l&&t>=r){ 107 tmp+=sm[rt]-sm[pr]; 108 return; 109 } 110 int mid=(l+r)>>1; 111 if(s<=mid)query(l,mid,lc[rt],lc[pr]); 112 if(t>mid)query(mid+1,r,rc[rt],rc[pr]); 113 }
一个细节:
一开始觉得二分答案可以直接换成一边倍增上跳一边判定当前点是否可行,后来发现这样是错的,因为答案不一定是c的某个祖先的深度(比如有个点深度是2,父亲的深度是0,可是答案是1)……当然判定当前点是否可行的时候再二分一下也可以,不过这样好像会多一个log……
话说很久之前就想写这题了,然后题意各种弄不清+网上的后缀数组题解各种看不懂=无限期跳票,今天心血来潮读了一遍题才弄清题意,然后找了几份后缀数组的题解还是没怎么看懂……无奈自己脑补了一发后缀自动机的做法,然而为啥跑得这么慢……明明是同样的做法,我比ad学长慢了整整2s,比后缀数组众更是慢到不知哪里去了……