后缀自动机总结

Posted thy_asdf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀自动机总结相关的知识,希望对你有一定的参考价值。

后缀自动机总结

后缀自动机的构造和相关性质及复杂度证明可以看陈老师的ppt

时间复杂度据说可以用均摊分析证明是O(n)的

一开始看直接看陈老师的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;


以上是关于后缀自动机总结的主要内容,如果未能解决你的问题,请参考以下文章

leetcode-236-二叉树公共祖先

bzoj 2555 SubString —— 后缀自动机+LCT

bzoj 2555 SubString——后缀自动机+LCT

后缀自动机题单

数据结构题实操题:利用后序遍历的特点寻找最近公共祖先

BZOJ4545DQS的trie 后缀自动机+LCT