后缀自动机总结
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 queint 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];
elsep=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;
以上是关于后缀自动机总结的主要内容,如果未能解决你的问题,请参考以下文章
bzoj 2555 SubString —— 后缀自动机+LCT