[bzoj3238][后缀数组][单调栈][后缀自动机]差异

3238: [Ahoi2013]差异

Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 4128 Solved: 1869
[Submit][Status][Discuss]
Description

这里写图片描述

Input

一行,一个字符串S

Output

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

Sample Input

cacao
Sample Output

54

HINT

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

Source

sa:

前面的求和一下。后面的考虑从sa上面做。
答案是 inj=1ijminhei ∑ i ∑ j = 1 n i 到 j 的 m i n h e i
显然不能n^2做。考虑minhei影响到的区间,我们可以单调栈处理出来,然后就是他的左区间*右区间

#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;

const int Manx = 1010000;

int n, m, tot, p, t;
long long Ans;
int h[Manx], l[Manx], r[Manx];
int x[Manx], sa[Manx], wv[Manx], w[Manx], y[Manx], hei[Manx];
char Sr[Manx];

int main()
{
    int i, j;
    scanf("%s",Sr + 1);
    n = strlen(Sr + 1);
    m = 255;
    for(i = 1; i <= n; ++i) w [x[i] = Sr[i]] ++;
    for(i = 2; i <= m; ++i) w[i] += w[i - 1];
    for(i = n; i >= 1; --i) sa[w[x[i]] --] = i;
    for(j = 1; j <= n; j <<= 1)
    {
        m = p; 
        p = 0;
        for(i = n; i >= n - j + 1; --i) y[++ p] = i;
        for(i = 1; i <= n; ++i)
        if(sa[i] > j) y[++ p] = sa[i] - j;
        for(i = 1; i <= m; ++i) w[i] = 0;
        for(i = 1; i <= n; ++i) ++w[x[i]];
        for(i = 2; i <= m; ++i) w[i] += w[i - 1];
        for(i = n; i >= 1; --i) sa[w[x[y[i]]]--] = y[i];
        swap(x, y);
        p = 0;
        for(i = 1; i <= n; ++i)
        x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + j] == y[sa[i - 1] + j]) ? p : ++p;
        if(n == p) break;
    }
//  for(i = 1; i <= n; ++i) printf("%d ", sa[i]);
//  printf("\n");
    for(i = 1; i <= n; ++i)
    {
        tot -= (tot > 0);
        j = sa[x[i] - 1];
        while(Sr[i + tot] == Sr[j + tot]) tot++;
        hei[x[i]] = tot;
    }
    Ans +=  (long long)(n + 1) * n / 2 * (n - 1);
    hei[0] = -1; hei[n + 1] = -1;
    t = 0; h[t] = 0;
    for(i = 1; i <= n; ++i)
    {
        while(hei[h[t]] > hei[i]) t --;
        l[i] = h[t] + 1;
        h[++t] = i;
    }
    t = 0; h[t] = n + 1;
    for(i = n; i >= 1; --i)
    {
        while(hei[h[t]] >= hei[i]) t --;
        r[i] = h[t] - 1;
        h[++t] = i;
    }
    for(i = 1; i <= n; ++i)
    Ans -= (long long)2 * hei[i] * (r[i] - i + 1) * (i - l[i] + 1);
    printf("%lld",Ans);
    fclose(stdin);fclose(stdout);
}


sam:

前面定值求和即可。考虑后面怎么求。题目让我们求后缀的公共前缀,这玩意是sa求得,sam干不了这事。
我们考虑把串反转一下,那么久变成了求前缀的最长公共后缀。这个东西就好做了,我们考虑在parent树上跳的过程实际上就是不断的在取后缀的过程,那么对于2个前缀,我们就一直跳,直到跳到他们的lca,这必然是他们的最长公共后缀了。那么问题就变成了对于一个状态,他是多少个前缀的lca。简单dp

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;

int n,m;

inline int read()
{
    char c;
    bool pd=0;
    while((c=getchar())>'9'||c<'0')
    if(c=='-') pd=1;
    int res=c-'0';
    while((c=getchar())>='0'&&c<='9')
    res=(res<<3)+(res<<1)+c-'0';
    return pd?-res:res;
}
const int N=1100000;
char sr[N];
int fa[N],go[N][26],val[N],vis[N],last,tot;
inline void extend(int x)
{
    int p=last;
    int np=last=++tot;
    val[np]=val[p]+1;
    while(!go[p][x]) go[p][x]=np,p=fa[p];
    if(!p) fa[np]=1,++vis[1];
    else
    {
        int q=go[p][x];
        if(val[q]==val[p]+1) fa[np]=q,++vis[q];
        else
        {
            int nq=++tot;
            fa[nq]=fa[q];
            vis[nq]=2;
            fa[q]=fa[np]=nq;
            val[nq]=val[p]+1;
            for(int i=0;i<=25;++i) go[nq][i]=go[q][i];
            while(go[p][x]==q) go[p][x]=nq,p=fa[p];
        }
    }
}
int q[N];
int size[N];
long long ans;
int main()
{
//  freopen("3238.in","r",stdin);
//  freopen(".out","w",stdout);
    scanf("%s",sr+1);
    n=strlen(sr+1);
    m=n/2;
    for(int i=1;i<=m;++i) swap(sr[i],sr[n-i+1]);
    last=tot=1;
    for(int i=1;i<=n;++i)
    {
        size[tot+1]=1;
        extend(sr[i]-'a');
    }
    int t=0,w=0;
    for(int i=1;i<=tot;++i)
    if(!vis[i]) q[++w]=i;
    while(t<w)
    {
        int u=q[++t];
        if(!(--vis[fa[u]])) q[++w]=fa[u];
        ans-=(long long)size[fa[u]]*size[u]*val[fa[u]];
        size[fa[u]]+=size[u];
    }
    ans<<=1;
    ans+=(long long)(n+1)*n/2*(n-1);
    cout<<ans;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值