后缀自动机总结
Posted thy_asdf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀自动机总结相关的知识,希望对你有一定的参考价值。
后缀自动机总结
后缀自动机的构造和相关性质及复杂度证明可以看陈老师的ppt
一开始看直接看陈老师的ppt确实有点难以理解,但是陈老师的ppt确实是讲的最正规的一个
一些定义:right集合:后缀自动机中节点代表的子串的右端点位置构成的集合
mins/maxs:节点代表的串的最短长度和最长长度
现在开始进入正题:
spoj1811:后缀自动机入门题,对一个串建自动机,另一个在上面匹配,记录匹配长度最大值即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=500010; using namespace std; int n,m,last;char a[maxn],b[maxn]; struct TSam{ int ch[maxn][26],tot,dis[maxn],fa[maxn],root; void add(int pos){ int x=a[pos]-'a',p=last,np=++tot; last=np,dis[np]=pos; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=++tot; dis[nq]=dis[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q],fa[np]=fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; //printf("nq%d\n",dis[nq]); } } //printf("%d\n",dis[tot]); } }T; int main(){ scanf("%s%s",a+1,b+1),last=T.root=++T.tot; n=strlen(a+1),m=strlen(b+1); for (int i=1;i<=n;i++) T.add(i); int ans=0,len=0,p=T.root; for (int i=1;i<=m;i++){ int x=b[i]-'a'; if (T.ch[p][x]) len++,p=T.ch[p][x];//puts("cas1"); else{ while (p&&!T.ch[p][x]) p=T.fa[p]; if (!p) p=T.root,len=0; else len=T.dis[p]+1,p=T.ch[p][x]; //puts("cas2"); } ans=max(ans,len); //printf("len=%d\n",len); } printf("%d\n",ans); return 0; } /* alsdfkjfjkdsal fdjskalajfkdsla */
spoj1812&&bzoj2946:对一个串建后缀自动机,其他串在上面匹配,因为是求所有串的公共子串,所以每个点记录每个串最长匹配长度的最小值,最后找到所有点中最长的一个即可
一个注意事项就是,当走到一个点时,还要更新它的parent树上的祖先的匹配长度
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=500010; using namespace std; int n,cnt,sum[maxn],tmp[maxn],ans[maxn],res;char s[maxn]; struct Tsam{ int dis[maxn],ch[maxn][26],fa[maxn],tot,last,root,maxs[maxn]; void init(){last=tot=root=1;} int newnode(int d){return dis[++tot]=d,tot;} void add(int pos){ int x=s[pos]-'a',np=newnode(pos),p=last; last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void build(){ init(); scanf("%s",s+1),n=strlen(s+1); for (int i=1;i<=n;i++) add(i); for (int i=1;i<=tot;i++) ans[i]=dis[i]; } void Tsort(){ for (int i=1;i<=tot;i++) sum[dis[i]]++;//printf("%d\n",dis[i]); for (int i=1;i<=n;i++) sum[i]+=sum[i-1]; for (int i=1;i<=tot;i++) tmp[sum[dis[i]]--]=i; //for (int i=1;i<=tot;i++) printf("%d %d\n",tmp[i],dis[tmp[i]]); } void work(){ memset(maxs,0,sizeof(maxs)); int len=0,p=root; for (int i=1;i<=n;i++){ int x=s[i]-'a'; if (ch[p][x]) len++,p=ch[p][x]; else{ for (;p&&!ch[p][x];p=fa[p]); if (!p) p=root,len=0; else len=dis[p]+1,p=ch[p][x]; } maxs[p]=max(maxs[p],len); } for (int i=tot;i;i--){ int x=tmp[i]; ans[x]=min(ans[x],maxs[x]); if (maxs[x]&&fa[x]) maxs[fa[x]]=dis[fa[x]]; //printf("x=%d ans=%d\n",x,ans[x]); //要记得更新祖先,因为我们可能到这个点而没有经过祖先,如果这个点可以到达,那么祖先一定是可以到dis[fa[x]]的 //类似AC自动机里的fail树,我们能到fail树的儿子所代表的串,就一定能走到fail树祖先所代表的串 //这里的pre指针构成的树也是类似 //fa[x]所带表的一些串的right集合比x的大,且包含right[x],maxs[fa[x]]也比x要短 //所以fa[x]的串其实是x的串的子串,x能到,fa[x]当然也能到 } } }T; int main(){ /*//freopen("test.in","r",stdin); T.build(),T.Tsort(); while (scanf("%s",s+1)!=EOF) n=strlen(s+1),T.work();*/ scanf("%d",&cnt); T.build(),T.Tsort(); for (int i=1;i<cnt;i++) scanf("%s",s+1),n=strlen(s+1),T.work(); for (int i=1;i<=T.tot;i++) res=max(res,ans[i]); printf("%d\n",res); return 0; }
spoj8222:构建字符串的自动机,对于每个节点,right集合大小就是出现次数,maxs就是它代表的最长长度,那么我们用|right(x)|去更新f[maxs[x]]的值,最后从大到小用f[i]去更新f[i-1]的值即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=500010; using namespace std; int n,f[maxn],sum[maxn],tmp[maxn];char s[maxn]; struct Tsam{ int dis[maxn],ch[maxn][26],fa[maxn],r[maxn],tot,last,root; void init(){last=tot=root=1;} int newnode(int d){return dis[++tot]=d,tot;} void add(int pos){ int x=s[pos]-'a',p=last,np=newnode(pos); last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q],fa[np]=fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void work(){ init(); for (int i=1;i<=n;i++) add(i); for (int i=1,p=root;i<=n;i++) p=ch[p][s[i]-'a'],r[p]=1; for (int i=1;i<=tot;i++) sum[dis[i]]++; for (int i=1;i<=n;i++) sum[i]+=sum[i-1]; for (int i=1;i<=tot;i++) tmp[sum[dis[i]]--]=i; for (int i=tot;i;i--) r[fa[tmp[i]]]+=r[tmp[i]]; for (int i=1;i<=tot;i++) f[dis[i]]=max(f[dis[i]],r[i]); for (int i=n;i;i--) f[i]=max(f[i],f[i+1]); for (int i=1;i<=n;i++) printf("%d\n",f[i]); } }T; int main(){ scanf("%s",s+1),n=strlen(s+1); T.work(); return 0; }
bzoj2882:用后缀自动机实现最小表示法。把串复制一遍,构建后缀自动机,每次选择最小的边转移即可
因为字符集很大,所以转移边用map来存即可
#include<map> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define it map<int,int>::iterator const int maxn=1200010; using namespace std; int n,s[maxn]; void read(int &x){ char ch; for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } struct Tsam{ map<int,int> ch[maxn]; int dis[maxn],fa[maxn],root,last,tot; int newnode(int v){dis[++tot]=v;return tot;} void init(){last=root=++tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); last=np; for (;p&&!ch[p].count(x);p=fa[p]) ch[p][x]=np;//printf("char=%d %d\n",x,p); if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); ch[nq]=ch[q]; fa[nq]=fa[q]; fa[np]=fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;//printf("qp: char=%d %d %d\n",x,p,fa[p]); } } } void build(){ init(); for (int i=1;i<=n;i++) add(s[i]); for (int i=1;i<=n;i++) add(s[i]); } void getmin(){ for (int i=1;i<=tot;i++){ it iter=ch[i].begin(); //printf("id=%d char=%d minson=%d\n",i,iter->first,iter->second); } for (int i=1,p=root;i<=n;i++){ it iter=ch[p].begin(); printf(i==n?"%d\n":"%d ",iter->first); p=iter->second; //printf("p=%d %d\n",p,ch[p].begin()->second); } } }T; int main(){ //freopen("test.out","w",stdout); scanf("%d",&n); for (int i=1;i<=n;i++) read(s[i]); T.build(),T.getmin(); return 0; }
bzoj3998:
对于T=0的情况,right集合不管多大它也只算出现一次
对于T=1的情况,我们一遍DP需要求出每个点的right集合大小
然后扫一遍求出每个点下面还有多少个串
然后在自动机上走一走即可
另外直接按每个点代表的最长长度排序就可以得到拓扑序了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=1000010; using namespace std; int n,K,op;char s[maxn]; struct Tsam{ int dis[maxn],ri[maxn],ch[maxn][26],fa[maxn],sum[maxn],last,tot,root; int tmp[maxn],su[maxn]; void init(){last=root=tot=1;} int newnode(int v){dis[++tot]=v;return tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); ri[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void prework(){ init(); for (int i=1;i<=n;i++) add(s[i]-'a'); for (int i=1;i<=tot;i++) su[dis[i]]++; for (int i=1;i<=n;i++) su[i]+=su[i-1]; for (int i=tot;i;i--) tmp[su[dis[i]]--]=i; //for (int i=1;i<=tot;i++) printf("%d ",tmp[i]);puts(""); for (int i=tot;i;i--){ int p=tmp[i]; //printf("%d ",tmp[i]); if (op==1) ri[fa[p]]+=ri[p]; else ri[p]=1; }//puts(""); ri[root]=0; for (int i=tot;i;i--){ int p=tmp[i]; //printf("%d ",tmp[i]); sum[p]=ri[p]; for (int x=0;x<26;x++){ int son=ch[p][x]; if (son) sum[p]+=sum[son];//printf("p=%d son=%d sum=%d\n",p,son,sum[p]); } //printf("p=%d sum=%d\n",p,sum[p]); } //puts(""); } void dfs(int x,int rk){ //printf("%d %d\n",x,rk); if (rk<=ri[x]) return; rk-=ri[x]; for (int i=0;i<26;i++){ int son=ch[x][i]; if (son){ if (rk<=sum[son]){ putchar(i+'a'); dfs(son,rk); return; } rk-=sum[son]; } } } void work(){ if (K>sum[root]){puts("-1");return;} dfs(root,K);puts(""); } }T; int main(){ scanf("%s%d%d",s+1,&op,&K); n=strlen(s+1),T.prework(); T.work(); return 0; }poj1743:先考虑求一个出现次数>=2的最长子串
出现次数>=2那就只管|right(x)|>=2的点
然后对这些点的代表的子串长度取max即可
加上了不相交的限制,类似后缀数组的做法
我们就记录right集合的最大/最小值,他们的差值就是能不相交的最长长度
拿它和该点的maxs取min即可,然后对所有点的答案取max即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=40010; using namespace std; int n,a[maxn],tmp[maxn]; bool cmp(int a,int b); struct Tsam{ int fa[maxn],dis[maxn],l[maxn],r[maxn],ch[maxn][180],last,tot,root; void init(){ last=root=tot=1; memset(dis,0,sizeof(dis)); memset(l,63,sizeof(l)); memset(r,0,sizeof(r)); memset(fa,0,sizeof(fa)); memset(ch,0,sizeof(ch)); } int newnode(int v){dis[++tot]=v;return tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); last=np,l[np]=r[np]=dis[np]; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[np]=fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void work(){ int ans=0; for (int i=1;i<=tot;i++) tmp[i]=i; sort(tmp+1,tmp+1+tot,cmp); for (int i=1;i<=tot;i++){ l[fa[tmp[i]]]=min(l[fa[tmp[i]]],l[tmp[i]]); r[fa[tmp[i]]]=max(r[fa[tmp[i]]],r[tmp[i]]); } for (int i=1;i<=tot;i++) ans=max(ans,min(dis[i],r[i]-l[i])); printf("%d\n",ans<4?0:ans+1); } }T; bool cmp(int a,int b){return T.dis[a]>T.dis[b];} int main(){ for (scanf("%d",&n);n;scanf("%d",&n)){ for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<n;i++) a[i]=a[i+1]-a[i]+88; T.init(),n--; for (int i=1;i<=n;i++) T.add(a[i]); T.work(); } return 0; }
bzoj4566
对一个串A建自动机
另一个串B在上面匹配
考虑怎么统计答案
对于当前匹配到的点,那么它parent树中的祖先代表的串现在肯定也出现了
对于每个出现的点,它代表了maxs[x]-mins[x]+1个串(mins[x]=maxs[fa[x]]+1)
这些串出现了|right(x)|次,贡献就是|right(x)|*(maxs[x]-maxs[fa[x]])
因为要统计祖先的,所以把祖先的也累加到这个节点即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=400010; typedef long long ll; using namespace std; int n1,n2;char s1[maxn],s2[maxn]; struct Tsam{ int dis[maxn],fa[maxn],ri[maxn],ch[maxn][26],last,tot,root; int tmp[maxn],v[maxn]; ll sum[maxn]; void init(){last=root=tot=1;} int newnode(int v){dis[++tot]=v;return tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); ri[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } //printf("tot=%d\n",tot); } void prework(){ init(); for (int i=1;i<=n1;i++) add(s1[i]-'a'); } void Tsort(){ for (int i=1;i<=tot;i++) v[dis[i]]++; for (int i=1;i<=n1;i++) v[i]+=v[i-1]; for (int i=tot;i;i--) tmp[v[dis[i]]--]=i; } void DP(){ Tsort(); for (int i=tot;i;i--) ri[fa[tmp[i]]]+=ri[tmp[i]]; //for (int i=1;i<=tot;i++) printf("fa=%d ri=%d\n",fa[i],ri[i]); for (int i=1;i<=tot;i++){ int x=tmp[i]; sum[x]=sum[fa[x]]+1ll*ri[x]*(dis[x]-dis[fa[x]]);//printf("sum=%lld\n",sum[x]); } } void work(){ ll ans=0,len=0; for (int i=1,p=root;i<=n2;i++){ int x=s2[i]-'a'; if (ch[p][x]) p=ch[p][x],len++; else{ for (;p&&!ch[p][x];p=fa[p]); if (!p) p=root,len=0; else len=dis[p]+1,p=ch[p][x]; } if (p!=root) ans=ans+sum[fa[p]]+1ll*ri[p]*(len-dis[fa[p]]); //printf("p=%d\n",p); } printf("%lld\n",ans); } }T; int main(){ scanf("%s%s",s1+1,s2+1); n1=strlen(s1+1),n2=strlen(s2+1); T.prework(),T.DP(),T.work(); return 0; } /* asaaasaasasasasasasaasaaaasasas asasasaaasassssasasasasasasas 1285 */
bzoj3238:
反串的parent树是原串的后缀树
然后lcp就是他们的lca的深度,然后树形DP一下就好了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> typedef long long ll; const int maxn=1000010; using namespace std; int n;char s[maxn];ll ans; struct Tsam{ int dis[maxn],fa[maxn],ch[maxn][26],ri[maxn],sum[maxn],last,root,tot; int su[maxn],tmp[maxn]; void init(){last=root=tot=1;} int newnode(int v){dis[++tot]=v;return tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); ri[np]=sum[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void Tsort(){ for (int i=1;i<=tot;i++) su[dis[i]]++; for (int i=1;i<=n;i++) su[i]+=su[i-1]; for (int i=tot;i;i--) tmp[su[dis[i]]--]=i; } void treeDP(){ Tsort(); for (int i=tot;i;i--){ int x=tmp[i]; ri[fa[x]]+=ri[x]; } for (int i=tot;i;i--){ int x=tmp[i]; ans+=1ll*sum[fa[x]]*ri[x]*dis[fa[x]]; sum[fa[x]]+=ri[x]; } } }T; int main(){ scanf("%s",s+1),n=strlen(s+1),T.init(); for (int i=n;i;i--) T.add(s[i]-'a'); T.treeDP(); printf("%lld\n",1ll*(n+1)*n/2*(n-1)-ans*2); return 0; }
bzoj2806:
显然答案L具有可二分性
先在后缀自动机上匹配
求出mat[i]表示以待检查的作文的每个位置i为结尾最长能匹配多长
设f[i]表示前i个字符熟悉的部分最多有多长
那么f[i]=max(f[i-1],f[j]+i-j)
其中j要满足i-j>=L&&i-j+1<=mat[i]
整理得i-mat[i]<=j<=i-L
因为mat[i]+1<=mat[i+1]
所以i+1-mat[i+1]>=i+1-(mat[i]+1)=i-mat[i]
所以i-mat[i]单调不减,于是就可以用单调队列优化了
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=1200010*2,inf=0x3f3f3f3f; using namespace std; int n,n1,n2,f[maxn],mat[maxn];char s[maxn]; struct Tsam{ int dis[maxn],ch[maxn][3],fa[maxn],last,root,tot; void init(){last=root=tot=1;} int newnode(int v){dis[++tot]=v;return tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void match(){ mat[0]=0; for (int i=1,p=root;i<=n;i++){ int x=s[i]-'0'; if (ch[p][x]) mat[i]=mat[i-1]+1,p=ch[p][x]; else{ for (;p&&!ch[p][x];p=fa[p]); if (!p) p=root,f[i]=0; else mat[i]=dis[p]+1,p=ch[p][x]; } } //for (int i=1;i<=n;i++) printf("%d ",mat[i]); } }T; struct Tque{ int v[maxn],p[maxn],head,tail; void init(){head=1,tail=0;} void push(int val,int pos){ while (head<=tail&&val>v[tail]) tail--; v[++tail]=val,p[tail]=pos; } void pop(int lim){ while (head<=tail&&p[head]<lim) head++; } int top(){return v[head];} }q; bool check(int lim){ //printf("lim=%d\n",lim); memset(f,0,sizeof(int)*(n+5)); q.init(); for (int i=1;i<=n;i++){ f[i]=f[i-1]; if (i-lim>=0) q.push(f[i-lim]-(i-lim),i-lim); q.pop(i-mat[i]); //printf("left=%d right=%d\n",i-mat[i],i-lim); if (q.head<=q.tail) f[i]=max(f[i],q.top()+i);//printf("%d %d\n",i,q.top()); } //for (int i=1;i<=n;i++) printf("%d ",f[i]);puts(""); //for (int i=1;i<=n;i++) printf("%d ",mat[i]);puts(""); return f[n]>=n*0.9-1e-8; } void work(){ T.match(); //check(5);for (;;); int l=1,r=n,mid=(l+r)>>1,ans=0; while (l<=r){ if (check(mid)) l=mid+1,ans=mid; else r=mid-1; mid=(l+r)>>1; } printf("%d\n",ans); } int main(){ scanf("%d%d",&n1,&n2);T.init(); for (int i=1;i<=n2;i++){ scanf("%s",s+1),n=strlen(s+1); for (int j=1;j<=n;j++) T.add(s[j]-'0'); T.add(2); } for (int i=1;i<=n1;i++){ scanf("%s",s+1),n=strlen(s+1); work(); } return 0; }
bzoj2555:
一个点right集合就是它子树中所有点的right的并集
所以动态构建后缀自动机时,增加一个点,就把它的祖先的right集合都+1
因为parent树的形态会改变,所以用暴力动态树维护一下
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=1200010; using namespace std; int Q,n,mask,lastans=0;char s[maxn],op[maxn],ch[maxn]; void decode(int mask){ memcpy(s,ch,sizeof(char)*(n+5)); for (int j=0;j<n;j++){ mask=(mask*131+j)%n; char t=s[j]; s[j]=s[mask]; s[mask]=t; } } struct Tlct{ #define ls ch[x][0] #define rs ch[x][1] int val[maxn],ch[maxn][2],fa[maxn],add[maxn]; inline int which(int x){return ch[fa[x]][1]==x;} inline int isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;} inline void add_tag(int x,int v){add[x]+=v,val[x]+=v;} inline void down(int x){ if (add[x]!=0){ if (ls) add_tag(ls,add[x]); if (rs) add_tag(rs,add[x]); add[x]=0; } } void relax(int x){if (!isroot(x)) relax(fa[x]);down(x);} void rotate(int x){ int y=fa[x],z=fa[y],nx=which(x),ny=which(y),isrt=isroot(y); fa[ch[x][!nx]]=y,ch[y][nx]=ch[x][!nx]; fa[x]=z;if (!isrt) ch[z][ny]=x; fa[y]=x,ch[x][!nx]=y; } void splay(int x){ relax(x); while (!isroot(x)){ int y=fa[x]; if (isroot(y)) rotate(x); else if (which(x)==which(y)) rotate(y),rotate(x); else rotate(x),rotate(x); } } void access(int x){ for (int p=0;x;x=fa[x]) splay(x),rs=p,p=x; } void link(int x,int f){ fa[x]=f,access(f),splay(f),add_tag(f,val[x]); } void cut(int x){ access(x),splay(x),add_tag(ls,-val[x]); fa[ls]=0,ls=0; } #undef ls #undef rs }T; struct Tsam{ int fa[maxn],ch[maxn][26],dis[maxn],last,root,tot; int newnode(int v){return dis[++tot]=v,tot;} void link(int p,int f){fa[p]=f,T.link(p,f);} void add(int x){ int p=last,np=newnode(dis[p]+1); T.val[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) link(np,root); else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) link(np,q); else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); link(nq,fa[q]),T.cut(q); link(q,nq),link(np,nq); for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void init(){ last=root=tot=1; scanf("%s",s),n=strlen(s); for (int i=0;i<n;i++) add(s[i]-'A'); } void insert(){for (int i=0;i<n;i++) add(s[i]-'A');} int query(){ int p=root; for (int i=0;i<n;i++){ int x=s[i]-'A'; if (!ch[p][x]) return lastans=0; p=ch[p][x]; } T.splay(p);lastans=T.val[p]; return lastans; } }sam; int main(){ scanf("%d",&Q),sam.init(); while (Q--){ scanf("%s%s",op,ch); n=strlen(ch),decode(mask); if (op[0]=='A') sam.insert(); else printf("%d\n",sam.query()),mask=mask^lastans; } return 0; }
bzoj3926:
叶子数很少,所以我们可以枚举哪个叶子为根,这样就有了20个trie,对这20个trie建广义后缀自动机
然后统计有多少个不同的子串即可,不同子串个数就是Σ(dis[x]-dis[fa[x]])
<span style="font-size:14px;">#include<cstdio> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> const int maxn=100010,maxm=200010,maxt=2000010; using namespace std; int n,C,col[maxn],pre[maxm],now[maxm],son[maxm],deg[maxn],tot,last; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b,deg[b]++;} void read(int &x){ char ch; for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } struct Tsam{ int ch[maxt][11],fa[maxt],dis[maxt],root,tot; void init(){root=tot=1;} int newnode(int v){return dis[++tot]=v,tot;} void add(int x){ int p=last,q=ch[p][x]; if (q){ if (dis[q]==dis[p]+1) last=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; last=nq; } } else{ int np=newnode(dis[p]+1); for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[np]=fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } last=np; } } void work(){ long long ans=0; for (int i=1;i<=tot;i++) ans+=dis[i]-dis[fa[i]]; printf("%lld\n",ans); } }T; void dfs(int x,int f,int p){ T.add(col[x]); int t=last; for (int y=now[x];y;y=pre[y]) if (son[y]!=f) dfs(son[y],x,last),last=t; } int main(){ scanf("%d%d",&n,&C),T.init(); for (int i=1;i<=n;i++) read(col[i]); for (int i=1,x,y;i<n;i++) read(x),read(y),add(x,y),add(y,x); for (int i=1;i<=n;i++) if (deg[i]==1) dfs(i,0,last=T.root); T.work(); return 0; }</span>
bzoj3879
对要询问的点建出虚树,然后就和AHOI差异那题基本一样了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> typedef long long ll; const int maxn=500010*2,maxm=maxn*2; using namespace std; int n,m,a[maxn],stk[maxn],top,id[maxn],dfn[maxn],dep[maxn],tim,pw[25]; ll ans;char s[maxn];bool bo[maxn]; bool cmp(int a,int b){return dfn[a]<dfn[b];} void read(int &x){ char ch; for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } struct Tgra{ int pre[maxm],now[maxn],son[maxm],tot,fa[maxn][22]; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//printf("%d %d\n",a,b); void dfs(int x){ dfn[x]=++tim; for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int y=now[x];y;y=pre[y]) dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs(son[y]); } int lca(int x,int y){ if (dep[x]<dep[y]) swap(x,y); for (int h=dep[x]-dep[y],i=20;h;i--) if (h&pw[i]) h-=pw[i],x=fa[x][i]; if (x==y) return x; for (int i=20;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } }ori; struct Tsam{ int ch[maxn][26],fa[maxn],dis[maxn],cnt,last,root,tot; void init(){last=root=tot=1;} int newnode(int v){return dis[++tot]=v,tot;} int add(int x){ int p=last,np=newnode(dis[p]+1); last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } return np; } }T; struct Tgraph{ int pre[maxm],now[maxn],son[maxm],tot,sum[maxn]; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//printf("connect: %d %d\n",a,b); void dfs(int x){ //printf("x=%d\n",x); sum[x]=bo[x]; for (int y=now[x];y;y=pre[y]){ dfs(son[y]); ans+=1ll*sum[son[y]]*sum[x]*T.dis[x]; sum[x]+=sum[son[y]]; } now[x]=0; } }g; void init(){ pw[0]=1;for (int i=1;i<=20;i++) pw[i]=pw[i-1]<<1; scanf("%d%d%s",&n,&m,s+1),T.init(); //puts("%p"); for (int i=n;i;i--) id[i]=T.add(s[i]-'a'); for (int i=1;i<=T.tot;i++) ori.add(T.fa[i],i); ori.dfs(1); /*int qqq; scanf("%d",&qqq); for (int i=1,x,y;i<=qqq;i++) scanf("%d%d",&x,&y),printf("%d\n",ori.lca(x,y));*/ //for (int i=1;i<=n;i++) printf("i=%d id=%d\n",i,id[i]); } void work(){ //puts("%p"); int cnt;read(cnt); for (int i=1;i<=cnt;i++) read(a[i]),a[i]=id[a[i]]; sort(a+1,a+1+cnt,cmp),cnt=unique(a+1,a+1+cnt)-a-1; //for (int i=1;i<=cnt;i++) printf("keypoint: %d\n",a[i]); for (int i=1;i<=cnt;i++) bo[a[i]]=1; for (int i=1;i<=cnt;i++){ if (!top){stk[++top]=a[i];continue;} int u=ori.lca(a[i],stk[top]); while (top&&dep[u]<dep[stk[top]]){ if (dep[u]>=dep[stk[top-1]]){ g.add(u,stk[top]); if (stk[--top]!=u) stk[++top]=u; break; } --top,g.add(stk[top],stk[top+1]); } stk[++top]=a[i]; } while (--top) g.add(stk[top],stk[top+1]); ans=0,g.dfs(stk[1]),printf("%lld\n",ans); for (int i=1;i<=n;i++) bo[a[i]]=0;g.tot=0; } int main(){ init(); for (int i=1;i<=m;i++) work(); return 0; }
bzoj2780
先建出广义后缀自动机,建的时候对每个节点染色,表示它是属于哪个串
然后对每个询问串在自动机上匹配,匹配到的点即其parent树子树中的节点就可以包含这个串
于是问题就转化为给定一个颜色序列,每次询问一段区间内的颜色个数
这个就是bzoj1878HH的项链
离线,把询问按右端点排序,从左往右做,每次在树状数组中给当前位置+1,当前位置颜色的上一次出现位置-1,然后处理右端点在此的询问即可
本题一个点可能有多种颜色,所以挂个链表就好了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=200010,maxm=200010; using namespace std; int n1,n,Q,dfn[maxn],last[maxn],tim,seq[maxn],p[maxn],ans[maxn];char s[360010]; struct que{int l,r,id;}q[maxn]; bool cmp(que a,que b){return a.r<b.r;} struct Tgraph{ int pre[maxm],now[maxn],son[maxm],tot; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//,printf("a=%d b=%d\n",a,b) void dfs(int x){ dfn[x]=++tim,seq[tim]=x; for (int y=now[x];y;y=pre[y]) dfs(son[y]); last[x]=tim; } }g,chain; struct Tsam{ int ch[maxn][26],fa[maxn],dis[maxn],last,root,tot; void init(){last=root=tot=1;} int newnode(int v){return dis[++tot]=v,tot;} void add(int x,int id){ int p=last,q=ch[p][x]; if (q){ if (dis[q]==dis[p]+1) last=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; last=nq; } } else{ int np=newnode(dis[p]+1); for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } last=np; } //printf("chain: "); chain.add(last,id); //printf("%d\n",tot); } }T; struct BIT{ int val[maxn]; void add(int x,int v){ if (!x) return; for (;x<=T.tot;x+=x&(-x)) val[x]+=v; } int query(int x){ int res=0; for (;x;x-=x&(-x)) res+=val[x]; return res; } int query(int l,int r){ //int tmp=query(r)-query(l-1); //printf("tmp=%d\n",tmp); return query(r)-query(l-1); } }bit; void match(int id){ //printf("id=%d\n",id); int p=T.root; for (int i=1;i<=n;i++){ int x=s[i]-'a';//printf("x=%d p=%d\n",x,p); if (T.ch[p][x]) p=T.ch[p][x]; else{p=0;break;} } //printf("p=%d dfn=%d last=%d\n",p,dfn[p],last[p]); q[id]=(que){dfn[p],last[p],id}; } int main(){ scanf("%d%d",&n1,&Q),T.init(); for (int i=1;i<=n1;i++){ scanf("%s",s+1),n=strlen(s+1),T.last=1; for (int j=1;j<=n;j++) T.add(s[j]-'a',i); } for (int i=1;i<=T.tot;i++)/* printf("g: "),*/g.add(T.fa[i],i); g.dfs(1); //for (int i=1;i<=T.tot;i++) printf("id=%d dfn=%d\n",i,dfn[i]); for (int i=1;i<=Q;i++) scanf("%s",s+1),n=strlen(s+1),match(i); sort(q+1,q+1+Q,cmp); int st; for (st=1;st<=Q&&!q[st].l;st++); //printf("st=%d\n",st); for (int j=1;j<=T.tot;j++){ int x=seq[j]; //printf("id=%d x=%d\n",j,x); for (int y=chain.now[x];y;y=chain.pre[y]){ int col=chain.son[y]; bit.add(j,1); //printf("pre=%d col=%d\n",p[col],col); if (p[col]) bit.add(p[col],-1); p[col]=j; } for (;q[st].r==j;st++){ ans[q[st].id]=bit.query(q[st].l,q[st].r); //printf("ans=%d %d %d\n",ans[q[st].id],q[st].l,q[st].r); } } for (int i=1;i<=Q;i++) printf("%d\n",ans[i]); return 0; }
bzoj3756:
可以得到的串就是这个trie所表示的所有串的所有子串
把这个trie建广义后缀自动机,然后就类似bzoj4566的做法,在parent树上求出一些值,然后匹配一遍,求出答案就好了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> typedef long long ll; const int maxn=1600010,maxm=maxn; using namespace std; int n;char s[8000010];ll ans; struct Tsam{ int ch[maxn][4],fa[maxn],dis[maxn],tot,last,root,v[maxn],tmp[maxn],ri[maxn]; ll sum[maxn]; void init(){last=root=tot=1;} int newnode(int v){return dis[++tot]=v,tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); ri[np]=sum[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void Tsort(){ for (int i=1;i<=tot;i++) v[dis[i]]++; for (int i=1;i<=tot;i++) v[i]+=v[i-1]; for (int i=tot;i;i--) tmp[v[dis[i]]--]=i; } void work(){ Tsort(); for (int i=tot;i;i--){ int x=tmp[i]; if (fa[x]) ri[fa[x]]+=ri[x]; } for (int i=1;i<=tot;i++){ int x=tmp[i]; sum[x]=sum[fa[x]]+1ll*ri[x]*(dis[x]-dis[fa[x]]); } } void match(){ ans=0;int len=0; for (int p=root,i=1;i<=n;i++){ int x=s[i]-'a'; if (ch[p][x]) len++,p=ch[p][x]; else{ for (;p&&!ch[p][x];p=fa[p]); if (!p) p=root,len=0; else len=dis[p]+1,p=ch[p][x]; } if (fa[p]) ans+=1ll*(len-dis[fa[p]])*ri[p]+sum[fa[p]]; } printf("%lld\n",ans); } }T; struct Tgraph{ int pre[maxm],now[maxn],son[maxm],tot,val[maxn],last[maxn],q[maxn+10],head,tail; void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;} void bfs(){ q[tail=1]=1,head=0,last[1]=T.root; while (head!=tail){ int x=q[++head]; for (int y=now[x];y;y=pre[y]){ q[++tail]=son[y],T.last=last[x]; T.add(val[y]),last[son[y]]=T.last; } } } }g; int main(){ //freopen("test.in","r",stdin);freopen("test.out","w",stdout); scanf("%d",&n),T.init(); for (int i=2,x;i<=n;i++) scanf("%d%s",&x,s+1),g.add(x,i,s[1]-'a'); g.bfs(),scanf("%s",s+1),T.work(); n=strlen(s+1),T.match(); return 0; }
bzoj1396&&2865
首先只有|right(x)|=1的点才可以更新答案,设[l,r]是该点的最短的识别子串,l=maxs[x]-maxs[fa[x]],r=maxs[x]
然后我们可以发现它可以更新的区间有两段,[1,l-1]段可以用r-i+1来更新它的答案,因为我们要经过i这个位置
[l,r]段我们可以用r-l+1更新
于是我们可以开两个线段树来表示应用这两种区间覆盖,最后取min即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=1000010,maxt=maxn<<1,inf=1e9+7,pp=200000; using namespace std; int n;char s[maxn]; struct Tsegment{ #define ls (p<<1) #define rs (p<<1|1) #define mid ((l+r)>>1) int mins[maxt],cov[maxt]; void update(int p){mins[p]=min(mins[ls],mins[rs]);} void cover(int p,int v){mins[p]=min(mins[p],v),cov[p]=min(cov[p],v);} void down(int p){ if (cov[p]!=inf) cover(ls,cov[p]),cover(rs,cov[p]),cov[p]=inf; } void build(int p,int l,int r){ cov[p]=mins[p]=inf; if (l==r) return; build(ls,l,mid),build(rs,mid+1,r); } void modify(int p,int l,int r,int a,int b,int v){ if (l==a&&r==b){cover(p,v);return;} down(p); if (b<=mid) modify(ls,l,mid,a,b,v); else if (a>mid) modify(rs,mid+1,r,a,b,v); else modify(ls,l,mid,a,mid,v),modify(rs,mid+1,r,mid+1,b,v); update(p); } int query(int p,int l,int r,int x){ if (l==x&&r==x) return mins[p]; down(p); if (x<=mid) return query(ls,l,mid,x); else return query(rs,mid+1,r,x); } void modify(int l,int r,int v){modify(1,1,n,l,r,v);}//printf("l=%d r=%d v=%d\n",l,r,v); int query(int x){return query(1,1,n,x);} }t1,t2; struct Tsam{ int fa[maxn-pp],ch[maxn-pp][26],ri[maxn-pp],dis[maxn-pp],last,root,tot; void init(){last=root=tot=1;} int newnode(int v){return dis[++tot]=v,tot;} void add(int x){ int p=last,np=newnode(dis[p]+1); ri[np]=1,last=np; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=root; else{ int q=ch[p][x]; if (dis[q]==dis[p]+1) fa[np]=q; else{ int nq=newnode(dis[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } } void work(){ for (int i=1;i<=tot;i++) if (fa[i]) ri[fa[i]]=0; for (int i=1;i<=tot;i++) if (ri[i]==1){ int l=dis[i]-dis[fa[i]],r=dis[i]; //printf("%d %d %d %d\n",i,l,r,r-l+1); t1.modify(l,r,r-l+1); if (l>1) t2.modify(1,l-1,r); } for (int i=1;i<=n;i++) printf("%d\n",min(t1.query(i),t2.query(i)-i+1)); } }T; int main(){ scanf("%s",s+1),n=strlen(s+1),T.init(); for (int i=1;i<=n;i++) T.add(s[i]-'a'); t1.build(1,1,n),t2.build(1,1,n); T.work(); return 0; }
以上是关于后缀自动机总结的主要内容,如果未能解决你的问题,请参考以下文章