bzoj 4650: [Noi2016]优秀的拆分 后缀数组

题意

如果一个字符串可以被拆分为 AABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。例如,对于字符串 aabaabaa,如果令 A=aab,B=a,我们就找到了这个字符串拆分成 AABB 的一种方式。一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=a,B=baa,也可以用 AABB 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。现在给出一个长度为 n 的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。以下事项需要注意:出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。在一个拆分中,允许出现 A=B。例如 cccc 存在拆分 A=B=c。字符串本身也是它的一个子串。
T<=10,n<=30000

分析

这个做法还是比较巧妙的。

f[i] 表示有多少个重复串以 i 为开头,g[i]表示有多少个重复串以 i 结尾。

那么答案就是i=1n1g[i]f[i+1]

考虑如何求 f[i] g[i]

枚举重复串一半的长度 L ,在串上每L个位置取一个关键点。那么一个长度为 2L 的重复串的两半则分别经过两个相邻的关键点,且前半部分的每个位置 i 都有s[i]=s[i+L]

然后我们就可以用 sa 来求 lcp ,这样的话就相当于是给 f g的一段都 +1 ,只要差分维护一下就好了。

代码

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

typedef long long LL;

const int N=30005;

int n,bin[20],lg[N],b[N],c[N],d[N],f[N*2],g[N*2];
char str[N];

struct Suffix_Array
{
    int rank[N*2],sa[N],height[N],rmq[N][17],s[N];

    void get_sa()
    {
        for (int i=0;i<=30;i++) b[i]=0;
        for (int i=1;i<=n;i++) b[s[i]]++;
        for (int i=1;i<=30;i++) b[i]+=b[i-1];
        for (int i=n;i>=1;i--) c[b[s[i]]--]=i;
        int t=0,j=1;c[0]=0;
        for (int i=1;i<=n;i++)
        {
            if (s[c[i]]!=s[c[i-1]]) t++;
            rank[c[i]]=t;
        }
        while (j<=n)
        {
            for (int i=1;i<=n;i++) b[i]=0;
            for (int i=1;i<=n;i++) b[rank[i+j]]++;
            for (int i=1;i<=n;i++) b[i]+=b[i-1];
            for (int i=n;i>=1;i--) c[b[rank[i+j]]--]=i;
            for (int i=1;i<=n;i++) b[i]=0;
            for (int i=1;i<=n;i++) b[rank[i]]++;
            for (int i=1;i<=n;i++) b[i]+=b[i-1];
            for (int i=n;i>=1;i--) d[b[rank[c[i]]]--]=c[i];
            t=0;
            for (int i=1;i<=n;i++)
            {
                if (rank[d[i]]!=rank[d[i-1]]||rank[d[i]]==rank[d[i-1]]&&rank[d[i]+j]!=rank[d[i-1]+j]) t++;
                c[d[i]]=t;
            }
            for (int i=1;i<=n;i++) rank[i]=c[i];
            if (t==n) break;
            j<<=1;
        }
        for (int i=1;i<=n;i++) sa[rank[i]]=i;
    }

    void get_height()
    {
        int k=0;
        for (int i=1;i<=n;i++)
        {
            if (k) k--;
            int j=sa[rank[i]-1];
            while (i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
            height[rank[i]]=k;
        }
    }

    void build()
    {
        memset(rank,0,sizeof(rank));
        get_sa();
        get_height();
        for (int i=1;i<=n;i++) rmq[i][0]=height[i];
        for (int j=1;j<=lg[n];j++)
            for (int i=1;i+bin[j]-1<=n;i++)
                rmq[i][j]=min(rmq[i][j-1],rmq[i+bin[j-1]][j-1]);
    }

    int get_lcp(int x,int y)
    {
        x=rank[x];y=rank[y];
        if (x>y) swap(x,y);
        x++;int l=lg[y-x+1];
        return min(rmq[x][l],rmq[y-bin[l]+1][l]);
    }

}sa1,sa2;

int main()
{
    bin[0]=1;
    for (int i=1;i<=15;i++) bin[i]=bin[i-1]*2;
    for (int i=1;i<=30000;i++) lg[i]=log(i)/log(2);
    int T;scanf("%d",&T);
    while (T--)
    {
        scanf("%s",str+1);n=strlen(str+1);
        for (int i=1;i<=n;i++) sa1.s[i]=str[i]-'a'+1;
        sa1.build();
        for (int i=1;i<=n;i++) sa2.s[i]=str[n-i+1]-'a'+1;
        sa2.build();
        for (int i=1;i<=n;i++) f[i]=g[i]=0;
        for (int L=1;L<=n;L++)
            for (int i=1;i+L<=n;i+=L)
            {
                int j=i+L,s1=min(sa1.get_lcp(i,j),L),s2=min(sa2.get_lcp(n-i+1,n-j+1),L);
                int l=i-s2+1,r=i+s1-1;
                if (r-l+1<L) continue;
                f[l]++;f[r-L+2]--;
                g[l+L*2-1]++;g[r+L+1]--;

            }
        for (int i=1;i<=n;i++) f[i]+=f[i-1],g[i]+=g[i-1];
        LL ans=0;
        for (int i=1;i<n;i++) ans+=(LL)g[i]*f[i+1];
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值