您的位置:首页 > 其它

BZOJ 3238 差异(后缀自动机+树上统计)

2018-03-22 08:46 183 查看

3238: [Ahoi2013]差异

Time Limit:
20 Sec  Memory Limit:
512 MB
Submit:
4179  Solved:
1896

[Submit][Status][Discuss]


Description




Input

一行,一个字符串S


Output

 
一行,一个整数,表示所求值


Sample Input

cacao


Sample Output

54


HINT

2<=N<=500000,S由小写英文字母组成

        中文题,题意自己看看吧。
       看到表达式,难求的主要是lcp,任意两个后缀的lcp。即使用上后缀数组求这么多个lcp也是要超时的。但是这里如果用后缀自动机的性质,会好很多。

        我们之前在介绍后缀自动机的时候说过,parent树表示父亲是儿子最长的一个后缀。那么,对于最长公共后缀的时候,两个字符串对应状态的LCA的len值,就是我们最长公共后缀的长度。即两个字符串往上走,都是走到他们的后缀,然后越往上长度越短,所以说第一个相同后缀一定是最长的,对应长度就是LCA的len。对于本题的lcp,同样的道理,只需要我们再加入字符串的时候反过来倒着建立后缀自动机即可。
        建立完自动机之后,要考虑如何求和,因为直接枚举求还是会超时的。经典的套路,我们考虑每一个点作为LCA的总贡献。单个贡献显然就是那个点的len值,要求的就是该点作为LCA的次数。这个次数又是一个树上统计的经典问题,类似树形dp的方式,求该点每一个子树中有效点的累积乘积和即可。这里的有效点指不考虑后缀自动机中拆点操作时的点。

         至于表达式中前面两项,求个和可以得出前两项的和为(n-1)*n*(n+1)/2。用该答案减去lcp之和的两倍即可。具体见代码:

#include<bits/stdc++.h>
#define LL long long
#define N 500010
using namespace std;

struct Suffix_Automation
{
int tot,cur;
struct node{int ch[26],len,fa,r;} T[N<<1];
void init(){cur=tot=1;memset(T,0,sizeof(T));}

void ins(int x,int id)
{
int p=cur; cur=++tot; T[cur].len=id; T[cur].r++;                //标记为有效点
for(;p&&!T[p].ch[x];p=T[p].fa) T[p].ch[x]=cur;
if (!p) {T[cur].fa=1;return;}int q=T[p].ch[x];
if (T[p].len+1==T[q].len) {T[cur].fa=q;return;}
int np=++tot; memcpy(T[np].ch,T[q].ch,sizeof(T[q].ch));
T[np].fa=T[q].fa; T[q].fa=T[cur].fa=np; T[np].len=T[p].len+1;
for(;p&&T[p].ch[x]==q;p=T[p].fa) T[p].ch[x]=np;
}

} SAM;

vector<int> g[N<<1];
LL ans; char s
;

void dfs(int x)                        //类似树形dp求每个点的总贡献
{
for(int i=0;i<g[x].size();i++)
{
int y=g[x][i]; dfs(y);
ans-=(LL)SAM.T[x].r*SAM.T[y].r*SAM.T[x].len*2;           //有效点个数的乘积和
SAM.T[x].r+=SAM.T[y].r;            //累积起来
}
}

int main()
{
SAM.init();

scanf("%s",s);
LL n=strlen(s);
for(int i=n-1;i>=0;i--)
SAM.ins(s[i]-'a',n-i);
for(int i=2;i<=SAM.tot;i++)
g[SAM.T[i].fa].push_back(i);
ans=n*(n-1)*(n+1)/2;
dfs(1); printf("%lld\n",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: