BZOJ 3238 【AHOI2013】 差异

题目链接:差异

  写题时发现这道题当初已经用后缀数组写过了……但是既然学了后缀自动机那就再写一遍吧……

  观察一下题目所给的式子:\[\sum_{1\leqslant i < j \leqslant n}len(T_i)+len(T_j)-2lcp(T_i,T_j)\]

  可以发现前面两项其实是可以在\(O(n)\)的时间内算出来的。于是我们就只需要考虑后面那一项怎么算。

  题目要求的是所有后缀的最长公共前缀之和,那么把串反一下就变成了所有前缀的最长公共后缀之和。

  那么,我们构出翻转后的串的\(parent\)树(其实就是原串的后缀树),两个串的公共后缀就是他们在\(parent\)树上的\(lca\)节点包含的最长子串长度。

  然后我们就可以枚举每个节点作为\(lca\),然后统计一下它的两两子树\(right\)集合大小之积即可。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 1000010

using namespace std;
typedef long long llg;

char s[maxn>>1];
int n,tot,la,head[maxn],next[maxn],to[maxn],tt;
int son[maxn][26],len[maxn],fa[maxn],siz[maxn];
llg ans;

void link(int x,int y){to[++tt]=y;next[tt]=head[x];head[x]=tt;}
void insert(int p,int x){
	int np=++tot; siz[np]=1; len[np]=len[p]+1;
	for(;p && !son[p][x];p=fa[p]) son[p][x]=np;
	if(!p) fa[np]=1;
	else{
		int q=son[p][x];
		if(len[q]==len[p]+1) fa[np]=q;
		else{
			int nq=++tot;
			memcpy(son[nq],son[q],sizeof(son[q]));
			fa[nq]=fa[q]; fa[np]=fa[q]=nq; len[nq]=len[p]+1;
			for(;son[p][x]==q;p=fa[p]) son[p][x]=nq;
		}
	}
	la=np;
}

void dfs(int u){
	for(int i=head[u],v;v=to[i],i;i=next[i]){
		dfs(v);
		ans-=(llg)siz[u]*siz[v]*len[u],siz[u]+=siz[v];
	}
}

int main(){
	File("a");
	scanf("%s",s+1); n=strlen(s+1); tot=la=1;
	for(int i=n>>1;i;i--) swap(s[i],s[n-i+1]);
	for(int i=1;i<=n;i++) insert(la,s[i]-'a');
	for(int i=2;i<=tot;i++) link(fa[i],i);
	dfs(1); ans<<=1; ans+=((llg)(n+1)*n*(n-1))>>1;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2017-01-18 16:42  lcf2000  阅读(148)  评论(0编辑  收藏  举报