后缀自动机总结

从后缀自动机到广义后缀自动机——最详细攻略 广义后缀自动机 前置知识 广义后缀自动机基于下面的知识点 字典树(Trie树) 后缀自动机 请务必对上述两个知识点非常熟悉之后,再来阅读本文,特别是对于后缀自动机中的后缀链接能够有一定的理解 起源 广义后缀自动机是由刘研绎在其2015 国家队论文《后缀自动机在字典树上的拓展》上提出的一种结构,即将后缀自动机直接建立在字典树上。 大部分可以用后缀自动机处理的字符串的问题均可扩展到 Trie 树上。——刘研绎 约定 参考字符串约定 约定字符串的下标为 [1,∣s∣][1, |s|][1,∣s∣]。 约定 阅读详情

后缀自动机总结

后缀自动机的构造和相关性质及复杂度证明可以看陈老师的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 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;
}


后缀自动机SAM 后缀自动机SAM总结。 阅读详情

相关推荐

从最长公共子串到后缀自动机(LCS->SAM)

Longest Common Substring—Suffix Automaton本文将从最长公共子串开始说起,循序渐进的讲解后缀自动机,希望通过自己的理解帮助到大家,文章目录如下: 最长公共子串问题 后缀自动机历史 后缀自动机简介 后缀自动机相关理论基础 如何构造后缀自动机 后缀自动机的应用 总结 最长公共子串问题首先来看一到经典的例题题目:给出N个长度不超过100000的字符串,求他们的最长公共

菜的抠脚弟弟的专栏 4338

后缀自动机详解

原论文(俄文)地址:suffix_automata   后缀自动机   后缀自动机(单词的有向无环图)——是一种强有力的数据结构,让你能够解决许多字符串问题。 例如,使用后缀自动机可以在某一字符串中搜索另一字符串的所有出现位置,或者计算不同子串的个数——这都能在线性 时间内解决。   直觉上,后缀自动机可以被理解为所有子串的简明信息。一个重要的事实是,后缀自动机以压缩后的形式包含了一...

DZYO的博客 4万+

随机截距交叉滞后模型RI-CLPM:原理、Mplus实现与结果解读

纵向面板数据分析中,传统交叉滞后模型(CLPM)常被用于探索变量间的时序关系,但其估计结果可能混淆了个体间稳定差异与个体内短期波动,导致交叉滞后路径出现偏差。随机截距交叉滞后模型(RI-CLPM)通过引入随机截距因子,将个体特质层面的差异与个体内动态过程分离,从而提供更纯净的个体内效应估计。该模型在组织心理学、发展心理学、成瘾行为等研究领域迅速普及,并成为顶级期刊审稿时常见的稳健性检验要求。本文从原理出发,系统讲解RI-CLPM的模型构建、识别条件、Mplus实操代码、输出解读及常见陷阱,并结合协变量处理、

weixin_33525298的博客 271

【算法专题】后缀自动机SAM

后缀自动机是用于识别子串的自动机。 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格)。 刷题推荐:[后缀自动机初探],题目都来自BZOJ。 【Right集合】 后缀自动机真正优于后缀树的方面在于:结合了有限状态自动机,从而实现了O(n)的时空复杂度。 trans(s,str)表示s+str到达的状态。 ST(str)=trans(init,str)即包括了str这一...

weixin_33978044的博客 201

Anchor graph 对齐文献解读

Align then Fusion: Generalized Large-scale Multi-view Clustering with Anchor Matching Correspondences文献解读

atease0001的博客 1163

解决uc浏览器劫持video标签问题

解决uc视频悬浮播放 ,video标签被浏览器劫持 ,vide0适配

smile_y_y的博客 3449

后缀自动机(SAM)总结

后缀自动机,是一个能识别一个字符串的所有后缀的自动机。 考虑一种最简单的实现: 将一个字符串的所有后缀插入到一棵trie中。 如图: 由于一个字符串的本质不同子串是O(n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;2&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;)&amp;amp;amp;amp;amp;amp;amp;amp;amp;a

weixin_42465242的博客 508

【AI落地】AI生成测试用例,claude or gpt?(提效至少 50%)

gpt生成测试用例?当然好使啦,用好了提效50%+,用不好就让他变好!

青禾tester 1966

后缀自动机学习小记

简介后缀三姐妹:后缀数组,后缀自动机,后缀树。 后缀自动机:Suffix Automation,也叫SAM。 创立算法的思路来源:能不能构出一个自动机(本质就是一个有向图),能识别一个串的所有后缀。识别所有后缀基础想法把所有的后缀都放进一个trie里面,比如串aabbabd。 这样的状态太多了,怎么把状态数缩小。减小状态数的方法定义一个子串的right集合为这个子串在原串中出现的右端点集合

Facico的博客 5865

【CANoe】CAPL_ECU上电后首帧报文发出时间检测

【CANoe】CAPL_ECU上电后首帧报文发出时间检测,通过CANoe I/O口检测电压实现

星Yeah 的博客 2508

Apollo 9.0 速度非线性规划算法 – piecewise jerk speed nonlinear optimizer Ⅰ

二次规划的速度规划算法(PiecewiseJerkSpeedOptimizer)的存在两个问题: 这里以一张图来说明上面的问题,假设存在一个实际限速区间(或曲率很大需要降速的区间)[S1, S2],PiecewiseJerkSpeedOptimizer的做法是根据动态规划的st曲线(黑色),找到具体的时间区间[T1, T2],然后对区间[T1, T2]内所有的点施加约束或惩罚。但是二次规划平滑后的曲线(黄色)发生了变化,那么限速区间[S1, S2]对应的时间区间[T1, T2]也发生了变化,所以实际应该是对

WaiNgai 332

后缀自动机超详细

endpos。

qq_53237241的博客 1422

CodeX 登录报错 os error 10013?一键修复 CodeX 无法登录问题(亲测有效)

CodeX 登录时报 os error 10013?本文提供快速修复方案,实测有效,彻底解决登录报错:登录失败:failed to start login server: 以一种访问权限不允许的方式做了一个访问套接字的尝试。 (os error 10013)的问题💡

XiaoqiangClub的博客 2917

后缀自动机(SAM)

后缀自动机

qq_51274548的博客 2724

SAP S/4 HANA CO 成本模块实战指南

摘要:SAP S/4HANA CO(管理会计)模块是企业内部成本管理的核心工具。本文系统介绍了CO模块的组织结构(控制范围与会计科目表映射)、基础概念(计划值与预算值)以及6大子模块功能(成本要素、成本中心会计等)。重点解析了各子模块的应用场景与差异,包括成本归集、费用分摊、利润分析和产品成本控制,并对比了利润中心与成本中心、内部订单与销售订单的区别。通过事务代码示例和功能对比表,帮助用户快速掌握SAP CO模块的核心管理逻辑与实施要点。

金牌架构师 1125

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

【学习笔记】后缀自动机SAM

学了 24 个小时都没学懂……

七代目鸽影 4243

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

后缀自动机

后缀自动机(Suffix Automaton,SAM)是能存储和识别一个字符串S的所有后缀的自动机。一般用于处理一个长字符串中有多少字串的问题。后缀自动机是一种特别高效的算法与数据结构,能过做到在时间上和空间上都达到了O(n)的复杂度。即在一遍遍历后就可以得到一个字符串的所有子串。至于其复杂度的证明我们将略过,因为在学习完这篇笔记之后理解了后缀自动机的运行原理和构造机制就会明白为什么可以达到如此优秀的复杂度(其实主要原因的是因为减少了重复,每次都是逐个遍历字符串然后在自动机中插入节点(状态))。

2301_81191472的博客 1191

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

广义后缀自动机

广义后缀自动机: 传统后缀自动机是解决单个主串的匹配问题,广义后缀自动机可以用来解决多个主串的匹配问题。

若世界是黑暗,那我便是黎明。 弱者为何而战!? 7901

我终于学会了后缀自动机。

3个月前就说要学,结果真实拖到今天。 有些东西火候不到是真的学不会…………. 网上的教程不算多,而且大部分都是翻译作品,对自动机内各个部位的叫法五花八门,比如自动机里虚边,就有叫link(后缀链接),pre指针,fa数组(我真的不知道fa是什么的缩写)的,记录步数的有叫len的,step的(这俩倒是好理解),状态转移的有叫next的,叫trans的还有叫ch数组的(再次对大佬的起名能力感到震惊...

Cymbals的博客 5951

后缀自动机(SAM)& 广义后缀自动机

一、模板 1.后缀自动机 2.广义后缀自动机 二、题目 1.CF gym100956 K 2.(SPOJ8222) 3.(SPOJ) LCS(最长公共子串) 4.(SPOJ) LCS2 5.牛客多校2020第2场A 6.[SCOI2012]喵星球上的点名

micaudience的博客 657

后缀自动机解决各类问题

后缀自动机是能存储和识别一个字符串S的所有后缀的自动机,是一个有向无环图。endpos 实际上是一个集合。具体而言,endpos 是 的字串 在 中出现位置的末尾的集合。 因此,根据定义,显然有性质:(下面简写 endpos 为edp, 表示字串 的 edp 集合,所有 edp 集合相同的字串所构成的集合成为 edp 等价类)我们发现一个 edp 等价类里的所有字串具有类似的性质,因此我们考虑将所有 edp 等价类的字串变成一个点来看待。后缀自动机的巧妙处:用路径表示子串由于一个 edp 等价类中所

2301_80812282的博客 559

后缀自动机的理解

不懂什么叫目录标题的目录标题后缀自动机的预备知识position end set功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入...

a465798176的博客 937
上一篇: 刷题时间记录
下一篇: bzoj2698: 染色
thy_asdf
博客等级 码龄12年 16粉丝 186原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值