「后缀自动机」
前言
这比后缀数组难啊。
但似乎其实我并不觉得比sa好用。
很难懂,本来看了一天的证明现在屁都没剩,事实证明打板子才是对的。
应用
很多,但我都不会。
- 求第K大
- 本质不同的子串
- 求排名
- 多个串求最长公共串
- 其实还有很多神仙操作...
所以我为什么要写总结啊喂。
#include<bits/stdc++.h> using namespace std; const int N=5000; int n,lst,cnt,len[N],buc[N],ch[N][26],fa[N],mx[N],mn[N],rk[N]; char s[N]; inline void extend(int c){ int p=lst,np;np=lst=++cnt; len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++cnt; fa[nq]=fa[q];fa[q]=fa[np]=nq; len[nq]=len[p]+1; for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } int main(){ memset(mn,0x3f,sizeof mn); lst=cnt=1; scanf("%d%s",&n,s+1); for(int i=1;s[i];++i) extend(s[i]-'a'); int DD=strlen(s+1); for(int i=1;i<=cnt;++i) buc[len[i]]++; for(int i=0;i<=DD;++i) buc[i]+=buc[i-1]; for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i; for(int i=2;i<=n;++i){ scanf("%s",s+1); int l=strlen(s+1); int LCS=0,root=1; for(int j=1;j<=l;++j){ if(ch[root][s[j]-'a']){ root=ch[root][s[j]-'a']; mx[root]=max(mx[root],++LCS); } else{ while(root&&!ch[root][s[j]-'a']) root=fa[root]; if(!root)root=1,LCS=0; else{ LCS=min(LCS,len[root]); root=ch[root][s[j]-'a']; mx[root]=max(mx[root],++LCS); } } // printf("%d %d %d %d %d\n",i,j,LCS,root,ch[root][s[j+1]-'a']); } for(int i=cnt;i;--i){ mn[rk[i]]=min(mn[rk[i]],mx[rk[i]]); if(fa[rk[i]]) mx[fa[rk[i]]]=min(len[fa[rk[i]]],max(mx[fa[rk[i]]],mx[rk[i]])); mx[rk[i]]=0; } } int ans=0; for(int i=1;i<=cnt;++i) ans=max(ans,mn[rk[i]]); printf("%d\n",ans); return 0; }
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+50; int n,point_cnt,lst; int len[N],ch[N][26],fa[N],endpos[N],tra[N]; char s[N]; vector <int> v[N]; inline void extend(int c){ int p=lst,np;np=lst=++point_cnt; endpos[np]=1; v[len[np]=len[p]+1].push_back(np); for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++point_cnt; fa[nq]=fa[q];fa[np]=fa[q]=nq; v[len[nq]=len[p]+1].push_back(nq); for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } #define xx endpos[v[i][j]]*(endpos[v[i][j]]-1)/2 int ans; signed main(){ lst=point_cnt=1; scanf("%s",s+1); n=strlen(s+1); reverse(s+1,s+n+1); for(int i=1;i<=n;++i) extend(s[i]-'a'); ans=(1+n)*n/2*(n-1); for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) endpos[fa[v[i][j]]]+=endpos[v[i][j]]; for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) tra[v[i][j]]+=xx,tra[fa[v[i][j]]]-=xx; for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) ans-=2*len[v[i][j]]*tra[v[i][j]]; printf("%lld\n",ans); return 0; }
例题
A. 弦论
对于一个给定长度为N的字符串,求它的第K小子串是什么。分两种情况:不同位置的相同子串算1个/多个。
$sam$跑出来,对$DAG$跑拓扑,可以通过对长度$len$排序求出拓扑序,倒着更新$f[i]$和$g[i]$表示$DAG$上的子串数量。
转移$f[i]=(\sum f[j])+1,g[i]=(\sum g[j])+endpos[i]$,其中$endpos[]$表示这个位置表示的字符串在串中的结尾位置,其实也就是在串中的出现次数。
$endpos$也需要转移,只不过因为它关乎$parent\ tree$,所以它要在树上转移,转移也很好转移,把枚举$ch$变成把它的贡献给$fa$就对了。
此位置卡住了我,因为我开始学的时候并不会按长度排序,只是正常建边然后找出拓扑序再更新$endpos$,但这样求出来是错的$endpos$。
因为对于$parent\ tree$上的父子关系,在$sam$上并不具有明确的拓扑关系,因为在$sam$上我的$fa$并不一定和我连边了。
我们之所以按照长度排序,正是省去对$sam,parent\ tree$两个结构都考虑的麻烦。因为在$sam,parent\ tree$上的遍历情况都是满足长度递增的。
在求第K小的时候,按照在$sam$上的$ch$字典序做类似主席树的操作。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e6+50; int T,K,B,lst,point_cnt,fa[N],ch[N][26],len[N],f[N],g[N],endpos[N],head[N],to[N<<1],nxt[N<<1],deg[N],vis[N],sta[N],fuc[N]; char s[N]; inline void lnk(int x,int y){ if(!x||!y) return; to[++B]=y,nxt[B]=head[x],head[x]=B,deg[y]++; } bool cmp(int a,int b){return len[a]>len[b];} inline void extend(int c){ int p=lst,np;np=lst=++point_cnt; len[np]=len[p]+1; endpos[np]=1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++point_cnt; fa[nq]=fa[q]; len[nq]=len[p]+1; for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; fa[q]=fa[np]=nq; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } void dfs1(int x){ vis[x]=1; for(int i=0;i<26;++i) if(ch[x][i]){ lnk(ch[x][i],x); if(!vis[ch[x][i]]) dfs1(ch[x][i]); } } signed main(){ lst=point_cnt=1; scanf("%s%lld%lld",s+1,&T,&K); for(int i=1;s[i];++i) extend(s[i]-'a'); dfs1(1); for(int i=1;i<=point_cnt;++i) fuc[i]=i; sort(fuc+1,fuc+point_cnt+1,cmp); for(int i=1;i<=point_cnt;++i) endpos[fa[fuc[i]]]+=endpos[fuc[i]]; for(int i=1;i<=point_cnt;++i) if(!deg[i]) sta[++sta[0]]=i; for(int i=1;i<=sta[0];++i){ int x=sta[i]; f[x]+=endpos[x];g[x]++; for(int i=head[x];i;i=nxt[i]){ f[to[i]]+=f[x];g[to[i]]+=g[x]; if(--deg[to[i]]==0) sta[++sta[0]]=to[i]; } } if((T==0&&g[1]<K)||(T==1&&f[1]<K)) return !puts("-1"); int x=1; while(x){ // printf("%d %d\n",g[1],f[1]); if(x!=1) K-=(T==0?1:endpos[x]); if(K<1) break; for(int i=0;i<26;++i) if(ch[x][i]) { if(T==0){ if(K>g[ch[x][i]]) K-=g[ch[x][i]]; else {printf("%c",'a'+i);x=ch[x][i];break;} } else{ if(K>f[ch[x][i]]) K-=f[ch[x][i]]; else {printf("%c",'a'+i);x=ch[x][i];break;} } } } return 0; }
B. 诸神眷顾的幻想乡
广义后缀自动机。
看到叶子很少,就有了一条性质:树上的任何一条路径都可以变成从一个叶子走到了另一个叶子。
于是把原树转化成了若干条串,答案就是这些串的本质不同的子串数。
跑广义后缀自动机有两种做法:
建$trie$,接着$bfs$建$sam$,这个点的$lst$是它在$trie$上的$fa$。
一个串一个串的跑,每次的$lst$置为1。
和普通$sam$不一样的地方是
inline void extend(int c){ int p=lst,np,q,nq; if(ch[p][c]){ q=ch[p][c]; if(len[q]==len[p]+1) return lst=q,void(); lst=nq=++cnt; fa[nq]=fa[q]; fa[q]=nq; len[nq]=len[p]+1; for(int i=0;i<C;++i) ch[nq][i]=ch[q][i]; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } else{ lst=np=++cnt;len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) return fa[np]=1,void(); q=ch[p][c]; if(len[q]==len[p]+1) return fa[np]=q,void(); nq=++cnt;len[nq]=len[p]+1; fa[nq]=fa[q];fa[q]=fa[np]=nq; for(int i=0;i<C;++i) ch[nq][i]=ch[q][i]; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } }
当有过这样的$ch[p][c]\& \& len[q]==len[p]+1$时,直接返回$q$节点。
还有DC讲过的不要np的情况,但我觉得太难记了,实际上。
inline void extend(int c){ int p=lst,np,q,nq; if(ch[p][c]&&len[ch[p][c]]==len[ch[p][c]]+1) return lst=ch[p][c],void(); lst=np=++cnt;len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) return fa[np]=1,void(); q=ch[p][c]; if(len[q]==len[p]+1) return fa[np]=q,void(); nq=++cnt;len[nq]=len[p]+1; fa[nq]=fa[q];fa[q]=fa[np]=nq; for(int i=0;i<C;++i) ch[nq][i]=ch[q][i]; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq; }
这样也是可以的,就是不去考虑不建np的情况,这样还比较好理解。
C. 公共串
计算几个串的最长公共子串。
做法:
用一个串跑$sam$,把其它几个串在它上面跑LCS,因为$sam$包含了所有的子串所以一直跑,一直跑到没有这个节点。
接着跳$fa$,因为已经匹配了很多了,现在要保留尽可能多的后缀,所以跳最长后缀$fa$,对每个点维护$mx$记录这个串跑到这个点的最长匹配长度
再维护$mn$表示所有几个串在该点匹配长度的最小值,因为要的是所有串的最长公共子串,有关的一个问题是:
在维护$mx$时,要进行一步把它的$mx$给$fa$的操作,原因是如果能考虑到这个点,那么就一定能匹配它的$fa$。
其实我认为还有一步操作是把$fa$的$mx$给它,不过$skyh$说贡献答案的话只需要满足有一个点能有所有串的$mx$就行了,而我们把$mx$给了$fa$就可以在$fa$处统计答案了。
然而我还有问题....抱歉这道题作假了
D. 差异
两个后缀的$lcp$就是这个串翻转之后的$parent\ tree$上的$lca$的$len$,因为$lca$是它们的最长公共后缀,相当于翻转前的后缀的最长公共前缀。
这样的话问题转化为统计每个$parent\ tree$的点作为$lca$的次数了,用它的C-它的儿子们的C就行了。
在做这道题时还疑问了很久为啥没有实际含义的$nq$也要作为$lca$被统计。
可以这样理解,因为$nq$是$q$的一个副本,而一切的$q$都追本溯元成为有意义的点了。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+50; int n,point_cnt,lst; int len[N],ch[N][26],fa[N],endpos[N],tra[N]; char s[N]; vector <int> v[N]; inline void extend(int c){ int p=lst,np;np=lst=++point_cnt; endpos[np]=1; v[len[np]=len[p]+1].push_back(np); for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++point_cnt; fa[nq]=fa[q];fa[np]=fa[q]=nq; v[len[nq]=len[p]+1].push_back(nq); for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } #define xx endpos[v[i][j]]*(endpos[v[i][j]]-1)/2 int ans; signed main(){ lst=point_cnt=1; scanf("%s",s+1); n=strlen(s+1); reverse(s+1,s+n+1); for(int i=1;i<=n;++i) extend(s[i]-'a'); ans=(1+n)*n/2*(n-1); for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) endpos[fa[v[i][j]]]+=endpos[v[i][j]]; for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) tra[v[i][j]]+=xx,tra[fa[v[i][j]]]-=xx; for(int i=n;i;--i) for(int j=0;j<(int)v[i].size();++j) ans-=2*len[v[i][j]]*tra[v[i][j]]; printf("%lld\n",ans); return 0; }
E. 工艺
最小表示法$O(n)$。
或者把原串再接一遍跑$sam$,答案就是从起点开始贪心走$n$步的字符串。
做题时其实是letong,我又作假题了有疑问,会不会有按照最小字符走到某个节点走不动了的情况呢?
答案是不会的,因为如果有这样的边即字符,那一定是相当于从第二次接的串出发的,那这样的字符结构一定会有两次出现,因为接了两次,
那么两次该字符的$endpos$都在这个点上,也就是说没有边了就相当于走前面那个点的边了。
#include<bits/stdc++.h> using namespace std; const int N=2e6+50; map <int,int> ch[N]; int n,lst,point_cnt; int len[N],fa[N],d[N]; inline void extend(int c){ int p=lst,np;np=lst=++point_cnt; len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++point_cnt; len[nq]=len[p]+1; fa[nq]=fa[q]; ch[nq]=ch[q]; fa[np]=fa[q]=nq; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } int main(){ lst=point_cnt=1; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&d[i]),extend(d[i]); for(int i=1;i<=n;++i) extend(d[i]); int x=1,cnt=0; while(++cnt<=n) printf("%d ",(*ch[x].begin()).first),x=(*ch[x].begin()).second; return 0; }
#include<bits/stdc++.h> #define N 600005 using namespace std; int n,s[N]; inline int rd(){ register int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) f=ch=='-'?-1:1,ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x*f; } int main(){ n=rd(); for(int i=1;i<=n;++i) s[i]=s[n+i]=rd(); int i=1,j=2,k; while(i<=n&&j<=n){ for(k=0;k<=n&&s[i+k]==s[j+k];k++); if(k==n) break; if(s[i+k]>s[j+k]){ i=i+k+1; if(i==j) ++i; } else{ j=j+k+1; if(i==j) ++j; } } int st=min(i,j); for(int i=1;i<=n;++i) printf("%d ",s[st+i-1]); return 0; }
F. 生成魔咒
这么吓唬人 的题这么水。
求不同本质的串的个数。
因为我们得知一个点代表的串的长度一定是连续的。
所以这个点代表的字符串数量就是$len[i]-minlen[i]+1=len[i]-len[fa[i]]$
把每个点的答案都统计就是答案了。
数太大了,就用$map$存$ch$就行了。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5+50; map <int,int> ch[N]; int B,point_cnt,lst,ans,n,fa[N],len[N];//,head[N],to[N<<1],nxt[N<<1],deg[N],sta[N],f[N]; /*inline void lnk(int x,int y){ to[++B]=y,nxt[B]=head[x],head[x]=B,deg[y]++; }*/ inline void extend(int c){ int p=lst,np; lst=np=++point_cnt; len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++point_cnt; fa[nq]=fa[q]; ch[nq]=ch[q]; len[nq]=len[p]+1; fa[q]=fa[np]=nq; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } ans+=len[lst]-len[fa[lst]]; printf("%lld\n",ans); } /*void dfs(int x,int prt){ f[x]=1; for(map <int,int> ::iterator it=ch[x].begin();it!=ch[x].end();++it) dfs((*it).second,x),f[x]+=f[(*it).second]; }*/ signed main(){ scanf("%lld",&n); point_cnt=1; lst=1; for(int i=1,x;i<=n;++i) scanf("%lld",&x),extend(x); //dfs(1,0); /*sta[0]=0; for(int i=1;i<=point_cnt;++i){if(!deg[i]) sta[++sta[0]]=i;f[i]=1;} for(int i=1;i<=sta[0];++i){ int x=sta[i]; for(int i=head[x];i;i=nxt[i]){ f[to[i]]+=f[x]; if(--deg[to[i]]==0) sta[++sta[0]]=to[i]; } }*/ //printf("%d\n",f[1]-1); return 0; }
G. SubString
因为要动态维护$endpos$集合,所以要用$lct$动态维护,那么添加一个点时就把$split(1,x)$,然后链加即可。
中间还有一个问题,就是$nq$,$nq$位置要直接把它的$endpos$赋值为$endpos[q]$。
那么就是单点修改,链修改,单点查询了。
#include<bits/stdc++.h> using namespace std; const int N=2e6+50; int Q,n,lst,cnt,mask; int cpy[N],endpos[N],len[N],ch[N][26],fa[N],rk[N],buc[N]; char s[N],str[N]; inline void update(int x,int delta){ while(x) endpos[x]+=delta,x=fa[x]; } inline void extend(int c){ int p=lst,np;np=lst=++cnt; endpos[np]=1; len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++cnt; fa[nq]=fa[q],fa[q]=fa[np]=nq; endpos[nq]=endpos[q]; len[nq]=len[p]+1; for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } update(fa[np],endpos[np]); } inline void input(int tmp=mask){ scanf("%s",str); int newlen=strlen(str); for(int i=0;str[i];++i){ tmp=(tmp*131+i)%newlen; swap(str[i],str[tmp]); } } int main(){ lst=cnt=1; scanf("%d%s",&Q,s+1); n=strlen(s+1); for(int i=1;s[i];++i) extend(s[i]-'A'); for(;Q;--Q){ scanf("%s",str); if(str[0]=='A'){ input(); for(int i=0;str[i];++i) extend((s[++n]=str[i])-'A'); } else{ /*for(int i=1;i<=cnt;++i) endpos[i]=cpy[i]; for(int i=0;i<=n;++i) buc[i]=0; for(int i=1;i<=cnt;++i) buc[len[i]]++; for(int i=0;i<=n;++i) buc[i]+=buc[i-1]; for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i; for(int i=cnt;i;--i) if(fa[rk[i]]) endpos[fa[rk[i]]]+=endpos[rk[i]];*/ input(); int res=0,root=1; for(int i=0;str[i];++i) root=ch[root][str[i]-'A']; res=root?endpos[root]:0; printf("%d\n",res); mask^=res; } } return 0; }
#include<bits/stdc++.h> using namespace std; const int N=2e6+50; int Q,n,lst,cnt,mask; int cpy[N],endpos[N],len[N],ch[N][26],fa[N],rk[N],buc[N]; char s[N],str[N]; struct LCT{ int prt[N],ch[N][2],sum[N],rev[N],tag[N]; inline int get(int x){return ch[prt[x]][1]==x;} inline int nroot(int x){return ch[prt[x]][0]==x||ch[prt[x]][1]==x;} inline void pushrev(int x){ rev[x]^=1; swap(ch[x][0],ch[x][1]); } inline void pushtag(int x,int Tag){ tag[x]+=Tag; sum[x]+=Tag; } inline void pushdown(int x){ if(rev[x]){ pushrev(ch[x][0]); pushrev(ch[x][1]); rev[x]=0; } if(tag[x]){ pushtag(ch[x][0],tag[x]); pushtag(ch[x][1],tag[x]); tag[x]=0; } } inline void pushup(int x){} inline void rotate(int x){ int fa=prt[x],gr=prt[fa],k=get(x); if(nroot(fa)) ch[gr][get(fa)]=x;prt[x]=gr; ch[fa][k]=ch[x][k^1];prt[ch[x][k^1]]=fa; ch[x][k^1]=fa;prt[fa]=x; pushup(fa);pushup(x); } void topushdown(int x){ if(nroot(x)) topushdown(prt[x]); pushdown(x); } inline void splay(int x){ topushdown(x); for(;nroot(x);rotate(x)) if(nroot(prt[x])) rotate(get(x)==get(prt[x])?prt[x]:x); } inline void access(int x){ for(int y=0;x;y=x,x=prt[x]) splay(x),ch[x][1]=y,pushup(x); } inline int findroot(int x){ access(x); splay(x); while(ch[x][0]) pushdown(x),x=ch[x][0]; return splay(x),x; } inline void makeroot(int x){ access(x); splay(x); pushrev(x); } inline void split(int x,int y){ makeroot(x); access(y); splay(y); } inline void link(int x,int y){ if(!x||!y) return; makeroot(x); if(findroot(y)==x) return; prt[x]=y; pushup(y); } inline void cut(int x,int y){ if(!x||!y) return; makeroot(x); if(findroot(y)!=x||ch[y][0]||prt[y]!=x) return; access(y); splay(x); ch[x][1]=prt[y]=0; pushup(x); } inline void exchange(int x,int Tag){ split(1,x); pushtag(x,Tag); } inline int endpos(int x){ split(x,x); return sum[x]; } inline void special_ex(int x,int Tag){ split(x,x); sum[x]+=Tag; } }lct; inline void extend(int c){ int p=lst,np;np=lst=++cnt; len[np]=len[p]+1; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++cnt; lct.cut(q,fa[q]); lct.link(nq,fa[q]); fa[nq]=fa[q],fa[q]=fa[np]=nq; lct.link(q,nq); lct.special_ex(nq,lct.endpos(q)); len[nq]=len[p]+1; for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } lct.link(np,fa[np]); lct.exchange(np,1); } inline void input(int tmp=mask){ scanf("%s",str); int newlen=strlen(str); for(int i=0;str[i];++i){ tmp=(tmp*131+i)%newlen; swap(str[i],str[tmp]); } } int main(){ lst=cnt=1; scanf("%d%s",&Q,s+1); n=strlen(s+1); for(int i=1;s[i];++i) extend(s[i]-'A'); for(;Q;--Q){ scanf("%s",str); if(str[0]=='A'){ input(); for(int i=0;str[i];++i) extend((s[++n]=str[i])-'A'); } else{ /*for(int i=1;i<=cnt;++i) endpos[i]=cpy[i]; for(int i=0;i<=n;++i) buc[i]=0; for(int i=1;i<=cnt;++i) buc[len[i]]++; for(int i=0;i<=n;++i) buc[i]+=buc[i-1]; for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i; for(int i=cnt;i;--i) if(fa[rk[i]]) endpos[fa[rk[i]]]+=endpos[rk[i]];*/ input(); int res=0,root=1; for(int i=0;str[i];++i) root=ch[root][str[i]-'A']; res=root?lct.endpos(root):0; printf("%d\n",res); mask^=res; } } return 0; }
其实不是很难打,思路也很简单。毕竟是模板题
在$hzoj$,暴力碾标算。
H. Cheat
题意:m个串,n次询问,每次询问这个串的L0,满足任意一段都$>=L0$。
把$m$个串建上广义$sam$,发现L0具有二分性,所以二分L0,然后问题就变成了:
把串分成若干个匹配串和不可匹配串,那么要求的匹配串的长度都必须大于二分的mid,要求$\sum$匹配串长度$>=0.9*len$,len为串长。
不妨设$f[i]$表示考虑到第$i$个字符时的最大匹配长度,那么有
$f[i]=max(f[i-1],f[j]+i-j),j\in [i-match[i],i-mid]$
$match$是以$i$为结尾的最长前缀,可以通过在$sam$上跑实现。
对于$f[i]$,$i$点可以不选就是$f[i-1]$,要选就必须选长度$>=mid$,而当长度$>match[i]$时再往前匹配就没有了意义,因为一定匹配不上,所以上下界确定了。
进一步发现,$i-match[i]$与$i-mid$是单调不降,单调增的,因此可以用单调栈维护,$check$的判断条件就是$f[n]>=0.9*len$,需要向上取整,或者$f[n]*10>=len*9$也可。
#include<bits/stdc++.h> using namespace std; const int N=4e6+50; int n,m,lst,cnt; int len[N],fa[N],ch[N][2],f[N],ret[N]; char s[N]; inline void extend(int c){ if(ch[lst][c]&&len[ch[lst][c]]==len[lst]+1) return lst=ch[lst][c],void(); int p=lst,np; for(len[np=lst=++cnt]=len[p]+1;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++cnt; len[nq]=len[p]+1; fa[nq]=fa[q],fa[q]=fa[np]=nq,ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1]; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } int lenn; struct Node{int pos,val;}que[N]; inline bool check(int k,int ans=0){ for(int i=0;i<=lenn;++i) f[i]=0; int head=1,tail=0; for(int i=1;i<=lenn;++i){ if(i-k>=0){ while(head<=tail&&que[tail].val<=f[i-k]-(i-k)) tail--; que[++tail]=(Node){i-k,f[i-k]-(i-k)}; } while(head<=tail&&que[head].pos<i-ret[i]) head++; f[i]=f[i-1]; if(head<=tail) f[i]=max(f[i],i+que[head].val); ans=max(ans,f[i]); } int d=0.9*lenn+0.99; return ans>=d; } inline void solve(){ scanf("%s",s+1); lenn=strlen(s+1); int LCS=0,root=1; for(int i=1;s[i];++i) if(ch[root][s[i]-'0']) root=ch[root][s[i]-'0'], ret[i]=++LCS; else{ for(;root&&!ch[root][s[i]-'0'];root=fa[root]) ; if(!root) root=1,ret[i]=LCS=0; else LCS=min(LCS,len[root]), root=ch[root][s[i]-'0'], ret[i]=++LCS; } int l=0,r=lenn; while(l<r){ int mid=(l+r+1)>>1; if(check(mid)) l=mid; else r=mid-1; } printf("%d\n",l); } int main(){ lst=cnt=1; scanf("%d%d",&n,&m); while(m--){ scanf("%s",s); lst=1; for(int i=0;s[i];++i) extend(s[i]-'0'); } while(n--) solve(); return 0; }
I. 品酒大会
题意:求所有后缀对的$lcp$。
想点对不好想,不妨考虑每个$parent\ tree$上的点作为$lcp$时的贡献,那么题目实际上就和D.差异类似了。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=6e5+50,inf=0x3f3f3f3f3f3f3f3f; int n,lst,cnt; int a[N],len[N],mx[N],mn[N],ch[N][26],fa[N],endpos[N]; char s[N]; vector <int> son[N]; pair <int,int> ans[N]; inline void extend(int c){ int p=lst,np; np=lst=++cnt; len[np]=len[p]+1; endpos[np]=1; mx[np]=mn[np]=a[n]; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++cnt; len[nq]=len[p]+1; fa[nq]=fa[q],fa[np]=fa[q]=nq; for(int i=0;i<26;++i) ch[nq][i]=ch[q][i]; for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } inline void dfs(int x){ for(int i=0;i<(int)son[x].size();++i){ int u=son[x][i]; dfs(u); ans[len[x]].first+=endpos[x]*endpos[u]; endpos[x]+=endpos[u]; if(mx[x]!=-inf&&mx[u]!=-inf) ans[len[x]].second=max(ans[len[x]].second,mx[x]*mx[u]); if(mn[x]!=inf&&mn[u]!=inf) ans[len[x]].second=max(ans[len[x]].second,mn[x]*mn[u]); mx[x]=max(mx[x],mx[u]); mn[x]=min(mn[x],mn[u]); } // printf("----------------%d %d %d\n",x,len[x],endpos[x]); } signed main(){ lst=cnt=1; scanf("%lld%s",&n,s+1); int T=strlen(s+1); reverse(s+1,s+T+1); for(int i=0;i<=n*2;++i) mx[i]=-inf,mn[i]=inf; for(int i=0;i<=n*2;++i) ans[i].second=-inf; for(int i=n;i;--i) scanf("%lld",&a[i]); for(n=1;s[n];++n) extend(s[n]-'a');--n; for(int i=1;i<=cnt;++i) son[fa[i]].push_back(i); dfs(1); for(int i=n-1;~i;--i) ans[i].first+=ans[i+1].first,ans[i].second=max(ans[i+1].second,ans[i].second); for(int i=0;i<n;++i) printf("%lld %lld\n",ans[i].first,ans[i].second==-inf?0:ans[i].second); return 0; }