洛谷4248 BZOJ3238 AHOI2013 差异 SAM 树形dp

题目链接

题意:
给你一个字符串, T i T_i Ti表示第 i i i个字符开始的后缀,求 ∑ 1 < = i < j < = n l e n ( T i ) + l e n ( T j ) − 2 ∗ l c p ( T i , T j ) \sum_{1<=i<j<=n}len(T_i)+len(T_j)-2*lcp(T_i,T_j) 1<=i<j<=nlen(Ti)+len(Tj)2lcp(Ti,Tj),其中 l e n ( i ) len(i) len(i)表示字符串 i i i的长度, l c p ( i , j ) lcp(i,j) lcp(i,j)表示字符串 i i i j j j的最长公共前缀。n<=500000。

题解:
首先我们可以化一下那个式子,有一个直觉是那个与字符串长度有关的部分可以直接算出来,那么我们先不管减lcp那一部分,会发现可以转化成 ∑ 1 < = i < j < = n l e n ( T i ) + l e n ( T j ) = ∑ i = 1 n − 1 ∑ j = i + 1 i + j = ( n − 1 ) ∗ n ∗ ( n + 1 ) / 2 \sum_{1<=i<j<=n}len(T_i)+len(T_j)=\sum_{i=1}^{n-1}\sum_{j=i+1}i+j=(n-1)*n*(n+1)/2 1<=i<j<=nlen(Ti)+len(Tj)=i=1n1j=i+1i+j=(n1)n(n+1)/2。那么我们就可以算出前面一部分,只要想办法减去lcp就可以了。后缀的LCP是比较容易用SAM算的,我们考虑建出SAM,然后我们发现,其实两个后缀的LCP长度就是parent树上根到LCA的字符串长度,那么我们就可以建出parent树,然后在树上进行树形dp,求出每个点为lca时对答案的贡献,其中字符串长度在建SAM时已经处理好了,所以在树形dp的同时再维护一下子树的size就好了。注意答案会爆int。

update:
再看时意识到,前缀的最长公共后缀之和与后缀的最长公共前缀之和是相同的,我这里写的是前缀的最长公共后缀,于是并没有把串翻转

代码:

#include <bits/stdc++.h>
using namespace std;

int n,fa[2000010],len[2000010],lst=1,rt=1,cnt=1,ch[2000010][26];
int sz[2000010],hed[2000010],num;
char s[500010];
long long ans; 
struct node
{
	int to,next;
}a[4000010];
inline void insert(int x)
{
	int cur=++cnt,pre=lst;
	lst=cur;
	len[cur]=len[pre]+1;
	sz[cur]=1;
	for(;pre&&!ch[pre][x];pre=fa[pre])
	ch[pre][x]=cur;
	if(!pre)
	fa[cur]=rt;
	else
	{
		int ji=ch[pre][x];
		if(len[ji]==len[pre]+1)
		fa[cur]=ji;
		else
		{
			int gg=++cnt;
			memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
			len[gg]=len[pre]+1;
			fa[gg]=fa[ji];
			fa[ji]=fa[cur]=gg;
			for(;pre&&ch[pre][x]==ji;pre=fa[pre])
			ch[pre][x]=gg;
		}
	}
}
inline void add(int from,int to)
{
	a[++num].to=to;
	a[num].next=hed[from];
	hed[from]=num;
}
inline void dfs(int x)
{
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		dfs(y);
		ans-=2ll*len[x]*sz[x]*sz[y];
		sz[x]+=sz[y];
	}
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i)
	insert(s[i]-'a');
	for(int i=2;i<=cnt;++i)
	add(fa[i],i);
	dfs(1);
	ans+=1ll*n*(n-1)/2*(n+1);
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值