【BZOJ4650】优秀的拆分(NOI2016)-后缀数组+RMQ+差分

测试地址:优秀的拆分
做法:本题需要用到后缀数组+RMQ+差分。
容易想到,令pre(i),nxt(i)分别为以点i结尾或开头的形如AA的字符串数,那么答案就是pre(i)nxt(i+1)。那么我们怎么求这两个数组呢?
对于一个长为L的字符串A,在它之后还有一个A的充要条件是:将字符串分成若干长度为L的段,那么字符串A必定经过或贴住一个分隔两段的点(以下简称为分割点),那么紧接在字符串A的一个长度为L的子串,也必定经过或贴住一个分割点,并且它和分割点的相对位置和字符串A相同,那么两个子串相同就等价为,对相邻的两个分割点,从两个分割点出发往左的一部分相等,往右的一部分也相等,这两个部分合起来长度为L
转化了这个条件之后,我们又能做什么呢?注意到上面的条件仅和相邻的分割点有关,所以我们枚举L,再枚举分割点,这样枚举的时间复杂度是O(nlogn)的。接下来我们要找到从这两个分割点往左的最长相同部分,注意到这就是两个前缀的最长公共后缀,把字符串倒过来做个后缀数组,再搭配ST表做RMQ可以做到O(1)询问。向右的公共部分同理,就是求两个后缀的最长公共前缀。那么只要那个长度为L的子串在第一个分割点向左向右划出的这么一个区间内,它的后面一个长为L的子串就和它相同。注意我们每次只考虑左端点在第一个分割点之前,到它前面一个分割点为止的子串的贡献。此时为了统计贡献,我们需要一个支持区间加的结构,显然可以差分,最后再做个前缀和就能得到prenxt了。这样我们就完成了此题,时间复杂度为O(nlogn)
(这道题好是好,就是这个数据的区分度太傻逼了,哈希暴力95,我后缀数组写挂都有65……完全没有让人写正解的诱惑力)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,x[30010],y[30010],s[30010],cnt[30010];
int SA[30010],Rank[30010],height[30010];
int rSA[30010],rrank[30010],rheight[30010];
int len[30010],f[30010][16],rf[30010][16];
ll pre[30010],nxt[30010];
char S[30010],rS[30010];

void init()
{
    scanf("%s",S+1);
    S[0]='#';
    n=strlen(S)-1;
    for(int i=1;i<=n;i++)
        rS[i]=S[n-i+1];
}

void calc_SA(char *S,int *rank,int *SA)
{
    for(int i=1;i<=n;i++)
        x[i]=S[i]-'a'+1;
    int m=26,p=1;
    while(p<n)
    {
        for(int i=1;i<=n;i++)
        {
            if (i+p<=n) y[i]=x[i+p];
            else y[i]=0;
        }

        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++) cnt[y[i]]++;
        for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
        for(int i=1;i<=n;i++) s[cnt[y[i]]--]=i;

        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++) cnt[x[i]]++;
        for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) SA[cnt[x[s[i]]]--]=s[i];

        m=0;
        for(int i=1;i<=n;i++)
        {
            if (i==1||x[SA[i]]!=x[SA[i-1]]||y[SA[i]]!=y[SA[i-1]]) m++;
            rank[SA[i]]=m;
        }
        if (m==n) break;
        for(int i=1;i<=n;i++) x[i]=rank[i];
        p<<=1;
    }
}

void calc_height(int *height,int *rank,int *SA,char *S)
{
    int last=0;
    for(int i=1;i<=n;i++)
    {
        if (rank[i]==n)
        {
            height[rank[i]]=last=0;
            continue;
        }
        while(S[i+last]==S[SA[rank[i]+1]+last]) last++;
        height[rank[i]]=last;
        last=max(last-1,0);
    }
}

void pre_rmq()
{
    int bit=0;
    for(int i=1;i<=n;i++)
    {
        if ((1<<(bit+1))<i) bit++;
        len[i]=bit;
    }
    for(int i=1;i<=n;i++)
        f[i][0]=height[i],rf[i][0]=rheight[i];
    for(int i=1;i<=15;i++)
        for(int j=1;j<=n-(1<<i)+1;j++)
        {
            f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]);
            rf[j][i]=min(rf[j][i-1],rf[j+(1<<(i-1))][i-1]);
        }
}

int LCP(int x,int y)
{
    if (x<1||x>n||y<1||y>n) return 0;
    int a=Rank[x],b=Rank[y],l=min(a,b),r=max(a,b)-1,p=len[r-l+1];
    return min(f[l][p],f[r-(1<<p)+1][p]);
}

int LCS(int x,int y)
{
    x=n-x+1,y=n-y+1;
    if (x<1||x>n||y<1||y>n) return 0;
    int a=rrank[x],b=rrank[y],l=min(a,b),r=max(a,b)-1,p=len[r-l+1];
    return min(rf[l][p],rf[r-(1<<p)+1][p]);
}

void work()
{
    memset(pre,0,sizeof(pre));
    memset(nxt,0,sizeof(nxt));
    for(int L=1;L<=(n>>1);L++)
        for(int i=L+1;i<=n;i+=L)
        {
            int x=LCP(i,i+L),y=LCS(i-1,i+L-1);
            if (x+y>=L)
            {
                int l=max(i-L,i-y),r=min(i-1,i+x-L);
                if (l>r) continue;
                nxt[l]++,nxt[r+1]--;
                pre[l+(L<<1)-1]++,pre[r+(L<<1)]--;
            }
        }
    for(int i=1;i<=n;i++)
        pre[i]+=pre[i-1],nxt[i]+=nxt[i-1];
    ll ans=0;
    for(int i=1;i<n;i++)
        ans+=pre[i]*nxt[i+1];
    printf("%lld\n",ans); 
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        calc_SA(S,Rank,SA);
        calc_height(height,Rank,SA,S);
        calc_SA(rS,rrank,rSA);
        calc_height(rheight,rrank,rSA,rS);
        pre_rmq();
        work();
    }

    return 0;
}
posted @ 2018-05-12 21:25  Maxwei_wzj  阅读(107)  评论(0编辑  收藏  举报