【AHOI2013】差异

后缀自动机

想到了其实挺简单的

首先对于后缀的前缀,我们不太好维护,所以我们可以考虑将字符串倒过来,这样就变成了维护前缀的后缀!

那么我们自然就想到了后缀自动机

然后我们观察这个式子发现恰好是是求parent树上任意两点路径和,那么我们在parent树上算一下每条边的贡献就好了。

对于一条边,他的贡献就是(len[x]-len[fa[x]])*si[x]*(n-si[x])

现在我们来细说一下为什么这个式子发现恰好是是求parent树上任意两点路径和

对于每条边来说,结束记父亲为fa,记儿子为son,那么fa一定是son的一个后缀,那么是后缀的这一段肯定不贡献,所有贡献的长度就是len[son]-len[fa],然后我们看贡献多少,首先有endpos(son)这么多位置fa一定是以这些位置为结尾的字符串的后缀,而有n-endpos(son)这么多位置可以作为开头,所以这条边的贡献就是上面说的那个式子!

时间效率:O(n)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
const int maxn=1e6+10;
int n;
char s[maxn];
struct suda
{
	struct da{int ch[30],fa,len;}po[maxn];
	int si[maxn],st[maxn],nt[maxn],to[maxn],topt,las,tot;
	long long ans;
	void init()
	{
		memset(po,0,sizeof po);
		memset(st,0,sizeof st);
		memset(si,0,sizeof si);
		las=tot=1; ans=0;
	}
	void insert(int x)
	{
		int p=las,np=las=++tot; po[np].len=po[p].len+1; si[np]=1;
		for (;p&&(!po[p].ch[x]);p=po[p].fa) po[p].ch[x]=np;
		if (!p) po[np].fa=1;
		else
		{
			int q=po[p].ch[x];
			if (po[q].len==po[p].len+1) po[np].fa=q;
			else
			{
				int nq=++tot; po[nq]=po[q];
				po[nq].len=po[p].len+1;
				po[q].fa=po[np].fa=nq;
				for (;p&&(po[p].ch[x]==q);p=po[p].fa) po[p].ch[x]=nq;
			}
		}
	}
	void add(int x,int y){to[++topt]=y; nt[topt]=st[x]; st[x]=topt;}
	void getsi(int x)
	{
		int p=st[x];
		while (p)
		{
			getsi(to[p]);
			si[x]+=si[to[p]];
			p=nt[p];
		}
		if (si[x]!=0) ans+=1ll*(po[x].len-po[po[x].fa].len)*si[x]*(n-si[x]);
	}
	long long solve()
	{
		for (int i=2;i<=tot;i++) add(po[i].fa,i);
		getsi(1); return ans;
	}
}SAM;
int main()
{
	scanf("%s",s+1); n=strlen(s+1); SAM.init();
	for (int i=1;i<=n;i++) SAM.insert(s[n-i+1]-'a');
	printf("%lld\n",SAM.solve());
return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值