AHOI 2013 差异 题解

题目传送门

题目大意: 给出一个字符串,求出这个柿子的值: ∑ 1 ≤ i < j ≤ n l e n ( T i ) + l e n ( T j ) − 2 × l c p ( T i , T j ) \sum\limits_{1\leq i<j\leq n} len(T_i)+len(T_j)-2\times lcp(T_i,T_j) 1i<jnlen(Ti)+len(Tj)2×lcp(Ti,Tj)

题解

显然 l e n ( T i ) + l e n ( T j ) len(T_i)+len(T_j) len(Ti)+len(Tj) 的和可以 O ( 1 ) O(1) O(1) 求出来,就是 ( n − 1 ) × n × ( n + 1 ) 2 \frac {(n-1)\times n\times (n+1)} 2 2(n1)×n×(n+1)

然后考虑后面如何求出任意两个后缀的最长公共前缀之和,把串反过来,就变成了求任意两个前缀的最长公共后缀。

建出SAM,考虑每一个状态的贡献,假设一个状态的 e n d p o s endpos endpos 集大小为 s i z e [ i ] size[i] size[i],就说明其中任意一个子串都恰好出现过 s i z e [ i ] size[i] size[i] 次,对于任意一个子串,假设他在 [ a , b ] [a,b] [a,b] [ c , d ] [c,d] [c,d] 出现过,那么以 b b b 结尾和以 d d d 结尾的前缀的最长公共后缀至少会延伸到 a , c a,c a,c 这两个位置,所以可以提供 1 1 1 的贡献,至于在 a + 1 a+1 a+1 c + 1 c+1 c+1 位置的匹配,自然会有 [ a + 1 , b ] [a+1,b] [a+1,b] [ c + 1 , d ] [c+1,d] [c+1,d] 两个子串来提供贡献 ,一共有 s i z e [ i ] × ( s i z e [ i ] − 1 ) size[i]\times(size[i]-1) size[i]×(size[i]1) 种这样的情况,所以贡献就是 s i z e [ i ] × ( s i z e [ i − 1 ] ) size[i]\times (size[i-1]) size[i]×(size[i1])

而一个状态内有 l e n ( i ) − l e n ( l i n k ( i ) ) len(i)-len(link(i)) len(i)len(link(i)) 个子串,所以贡献再乘上这个数量即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define ll long long

int n;
char s[maxn];
struct state{
	int len,link,next[26];
}st[maxn];
int id=0,last=0,now,p,q;
int size[maxn];
void extend(int x)
{
	now=++id;
	st[now].len=st[last].len+1;size[now]=1;
	for(p=last;p!=-1&&!st[p].next[x];p=st[p].link)st[p].next[x]=now;
	if(p!=-1)
	{
		q=st[p].next[x];
		if(st[p].len+1==st[q].len)st[now].link=q;
		else
		{
			int clone=++id;
			st[clone]=st[q];st[clone].len=st[p].len+1;
			for(;p!=-1&&st[p].next[x]==q;p=st[p].link)st[p].next[x]=clone;
			st[q].link=st[now].link=clone;
		}
	}
	last=now;
}
int c[maxn],A[maxn];
void get_size()
{
	for(int i=1;i<=id;i++)c[st[i].len]++;
	for(int i=1;i<=id;i++)c[i]+=c[i-1];
	for(int i=1;i<=id;i++)A[c[st[i].len]--]=i;
	for(int i=id;i>=1;i--)size[st[A[i]].link]+=size[A[i]];
}

int main()
{
	scanf("%s",s+1);n=strlen(s+1);st[0].link=-1;
	for(int i=n;i>=1;i--)extend(s[i]-'a');
	get_size(); ll ans=1ll*(n-1)*n*(n+1)/2ll;
	for(int i=1;i<=id;i++)ans-=1ll*(size[i]-1)*size[i]*(st[i].len-st[st[i].link].len);
	printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值