【BZOJ 3238】[Ahoi2013]差异 后缀自动机构造后缀树

关于后缀自动机的学习网上有很多,所以我就只说说为什么后缀自动机可以转成后缀树。

个人觉得比较好的后缀自动机的博客:http://blog.sina.com.cn/s/blog_70811e1a01014dkz.html

https://blog.csdn.net/litble/article/details/78997914

图片来源:http://blog.sina.com.cn/s/blog_70811e1a01014dkz.html

当我们构造出了后缀自动机以后(如上图),容易发现每一个节点和他的pre指针指向的节点到根节点之间的字串,他们都有着相同的后缀。而在后缀树当中,我们把所有的后缀压入一个trie树中,为了压缩空间每一条边代表的不仅仅是一个字符,而是一个字符串,同样的,如果两个后缀拥有相同的前缀,就可以公用这段空间。而刚才的后缀自动机中,我们通过pre指针找到了相同的后缀,那么我们将字符串倒过来就好了。而需要注意的是,由于nq指针是虚拟建立的,所以他不能作为后缀节点。

构造出来差不多就是这样啦,绿色节点表示后缀标记(说明走到这里就走完了一个后缀啦),边是一个字符串

dp很好写

 

#include<cstdio>
#include<cstring>
#include<iostream>
#define LL long long
#define maxn 500020*2
using namespace std;
int son[maxn][26],fail[maxn],size[maxn],head[maxn],len[maxn],cnt=1,last=1,tot=1,rt=1;
char s[maxn];LL ans;
struct edge{int v,next;}e[maxn*4];
void adde(int a,int b){e[cnt].v=b,e[cnt].next=head[a];head[a]=cnt++;}

void insert(int x){
	int p=last,np=++tot,q,nq;
	size[np]=1,len[np]=len[p]+1;
	while(p&&!son[p][x])son[p][x]=np,p=fail[p];
	if(p==0)fail[np]=rt;
	else{
		int q=son[p][x];
		if(len[q]==len[p]+1)fail[np]=q;
		else{
			nq=++tot;len[nq]=len[p]+1;
			memcpy(son[nq],son[q],sizeof(son[q]));
			fail[nq]=fail[q];
			fail[np]=fail[q]=nq;
			while(p&&son[p][x]==q)son[p][x]=nq,p=fail[p];
		}
	}
	last=np;
}

void dfs(int u,int fa){
	for(int v,i=head[u];i;i=e[i].next){
		dfs(v=e[i].v,u);
		size[u]+=size[v];
	}
	if(u==1)return;
	len[u]-=len[fa];
	ans-=(LL)len[u]*size[u]*(size[u]-1);
}

int main(){
	scanf("%s",s);int l=strlen(s);
	for(int i=l-1;i>=0;i--)insert(s[i]-'a');
	ans=(LL)l*(l-1)*(l+1)>>1ll;
	for(int i=2;i<=tot;i++)adde(fail[i],i);
	dfs(1,0);
	printf("%lld",ans);
	return 0;
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值