zoukankan      html  css  js  c++  java
  • [BZOJ]4650 优秀的拆分(Noi2016)

      比较有意思的一道后缀数组题。(小C最近是和后缀数组淦上了?)

      放在NOI的考场上。O(n^3)暴力80分,O(n^2)暴力95分……

      即使想把它作为一道签到题也不要这么随便啊摔(╯‵□′)╯︵┻━┻

    Description

      如果一个字符串可以被拆分为 AABB 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的。

      例如,对于字符串 aabaabaa,如果令 A=aab,B=a,我们就找到了这个字符串拆分成 AABB的一种方式。

      一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=a,B=baa,也可以用 AABB表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。

      现在给出一个长度为 n的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

      以下事项需要注意:

        1.出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。

        2.在一个拆分中,允许出现 A=B。例如 cccc 存在拆分 A=B=c。

        3.字符串本身也是它的一个子串。

    Input

      每个输入文件包含多组数据。输入文件的第一行只有一个整数 T,表示数据的组数。保证 1≤T≤10。

      接下来 T行,每行包含一个仅由英文小写字母构成的字符串 S,意义如题所述。

    Output

      输出 T行,每行包含一个整数,表示字符串 S 所有子串的所有拆分中,总共有多少个是优秀的拆分。

    Sample Input

      4
      aabbbb
      cccccc
      aabaabaabaa
      bbaabaababaaba

    Sample Output

      3
      5
      4
      7

    HINT

      

    Solution

      在考场上你只要负责打好95分的O(n^2)的字符串Hash就行了,代码总长只有30行。没有足够的时间和把握去打正解简直是作死。

      如果你有认真思考过这一题,你大概会肯定这题的正解就是后缀数组吧。

      设s[i]为字符串S的后缀,S[i]为字符串S的字符。

      一个很容易想到的结论:对于两个原串上的后缀s[i]、s[j](i<j),如果最长公共前缀LCP(s[i],s[j])>=j-i,那么可以得到一个AA串。

      如下图,i=1,j=3时,LCP(s[1],s[3])=3>3-1,得到AA串"abab"(S[1,4])。

        

      事实上,从上例这个结论还可以找到另外一个AA串"baba"(S[2,5])。

      于是我们发现,当i,j固定时,设len=j-i,Lcp=LCP(s[i],s[j])。

      如果Lcp>=len,我们可以找到从i开始的Lcp-len+1个AA串,A的长度为len,如下图:

        

      于是我们考虑找出所有这样的i、j,显然不可以直接枚举。

      但我们又发现,如果LCP(s[i],s[j])<j-i,那么就一定不存在以S[i]~S[j-1]为开头的AA串。

      那么感觉就可以跳着走?

      考虑枚举A的长度x,即j-i。枚举i,每次把i加上x。

      发现时间复杂度是调和级数,科学得不要不要的。

      但问题来了,只求s[i]、s[j]的LCP肯定是有遗漏的,我们还需要求出前缀p[i]、p[j]的最长公共后缀LCS。(解释见下图)

        

      那么判断存在AA串的条件就变成了Lcs+Lcp>len,可以找到Lcs+Lcp-len个AA串。

      

      问题又来了,如果按这样统计答案的话,一些开头可能会被重复统计。

      但是我们发现如果某一次的 i 还呆在上一次的 i 的Lcp和Lcs扩展出的区域时,它扩展出的区域将会和上一次是完全相同的。

      那么我们果断选择跳过。(如下图)

        

      求LCP的部分用ST表。

      于是我们就O(nlogn)求出了以每个S[i]为开头的AA串的数量。计算答案的思路和O(n^2)的做法是一样的。

      再求出以每个S[i]为结尾的AA串的数量,答案就是相邻两个字符开头数和结尾数相乘的总和……你懂的。

      时间复杂度O(Tnlogn)。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define ll long long
    #define INF 0x3FFFFFFF
    #define MS 17
    #define MN 30005
    using namespace std;
    int n,mp[MN],ht[MN],mi[MS],lg[MN];
    
    struct SufArr
    {
        int sa[2][MN],rk[2][MN<<1],mn[MS][MN<<1],p;
        bool mul(int* rk,int* sa,int* RK,int* SA,int K)
        {
            register int i;
            for (i=1;i<=n;++i) mp[rk[sa[i]]]=i;
            for (i=n;i;--i) if (sa[i]>K) SA[mp[rk[sa[i]-K]]--]=sa[i]-K;
            for (i=n-K+1;i<=n;++i) SA[mp[rk[i]]--]=i;
            for (i=1;i<=n;++i) RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+K]!=rk[SA[i-1]+K]);
            return RK[SA[n]]==n;
        }
        void presa(int* a)
        {
            register int i,j,k;
            memset(mp,0,sizeof(mp));
            for (i=1;i<=n;++i) ++mp[a[i]];
            for (i=1;i<=26;++i) mp[i]+=mp[i-1];
            for (i=1;i<=n;++i) sa[0][mp[a[i]]--]=i;
            for (i=1;i<=n;++i) rk[0][sa[0][i]]=rk[0][sa[0][i-1]]+(a[sa[0][i]]!=a[sa[0][i-1]]);
            for (i=p=1;i<n;i<<=1,p^=1) if (mul(rk[p^1],sa[p^1],rk[p],sa[p],i)) break;
            if (i>=n) p^=1;
            for (i=1,j=0;i<=n;++i)
            {
                for (k=sa[p][rk[p][i]-1];a[i+j]==a[k+j];++j);
                ht[rk[p][i]]=j; if (j) --j;
            }
            for (i=2;i<=n;++i) mn[0][i]=ht[i];
            for (i=1;i<MS;++i)
                for (j=2;j<=n;++j)
                    mn[i][j]=min(mn[i-1][j],(j+mi[i-1]>n?INF:mn[i-1][j+mi[i-1]]));
        }
        int getmn(int x,int y)
        {
            x=rk[p][x]; y=rk[p][y];
            if (x>y) swap(x,y);
            y=y-x; ++x;
            return min(mn[lg[y]][x],mn[lg[y]][x+y-mi[lg[y]]]);
        }
    }s1,s2;
    ll ans;
    int a[MN],b[MN],ltg[MN],rtg[MN],lgs[MN],rgs[MN];
    char c[MN];
    
    inline int read()
    {
        int n=0,f=1; char c=getchar();
        while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
        while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
        return n*f;
    }
    
    int main()
    {
        register int i,jl,jr,pre,x,y,T;
        T=read();
        for (mi[0]=1,lg[i=1]=0;i<MS;++i) mi[i]=mi[i-1]<<1,lg[mi[i]]=i; 
        for (i=1;i<MN;++i) if (!lg[i]) lg[i]=lg[i-1];
        while (T--)
        {
            memset(&s1,0,sizeof(s1));
            memset(&s2,0,sizeof(s2));
            memset(ltg,0,sizeof(ltg));
            memset(rtg,0,sizeof(rtg));
            scanf("%s",c+1); n=strlen(c+1); ans=0;
            a[n+1]=b[n+1]=rgs[n+1]=0;
            for (i=1;i<=n;++i) a[i]=c[i]-'a'+1; s1.presa(a);
            for (i=1;i<=n;++i) b[i]=a[n-i+1];   s2.presa(b);
            for (i=1;i<n;++i)
                for (jl=i,jr=jl+i,pre=0;jr<=n;jl=jr,jr+=i)
                {
                    if (jl<=pre) continue;
                    y=s1.getmn(jl,jr); x=s2.getmn(n-jl+1,n-jr+1);
                    if (x+y>i)
                    {
                        ++ltg[jl-x+1]; --ltg[jl+y-i+1];
                        ++rtg[jr+y-1]; --rtg[jr-x+i-1];
                    }
                    pre=jl+y-1;
                }
            for (i=1;i<=n;++i) lgs[i]=lgs[i-1]+ltg[i];
            for (i=n;i>=1;--i) rgs[i]=rgs[i+1]+rtg[i];
            for (i=2;i<=n;++i) ans+=1LL*lgs[i]*rgs[i-1];
            printf("%lld
    ",ans);
        }
    }

    Last Word

      又到了愉快的吐槽时间O(∩_∩)O~(你吐槽给谁看呢)

      这题基本就当做后缀数组的模板练习了,理解了模板敲起来就非常轻松了。

      反正正解小C大概是想不到的,大致方向是没有错,可是有时候就是差那么一点点,在深入些就是正解。

      但是离正解只差一步之遥却放弃的人不在少数啊。(但你至少可以拿到暴力分)

      写的时候因为几个数组没清空WA了几个点,后缀数组构建的时候要用到大于n的下标,真讨厌。

  • 相关阅读:
    CodeForces 734F Anton and School
    CodeForces 733F Drivers Dissatisfaction
    CodeForces 733C Epidemic in Monstropolis
    ZOJ 3498 Javabeans
    ZOJ 3497 Mistwald
    ZOJ 3495 Lego Bricks
    CodeForces 732F Tourist Reform
    CodeForces 732E Sockets
    CodeForces 731E Funny Game
    CodeForces 731D 80-th Level Archeology
  • 原文地址:https://www.cnblogs.com/ACMLCZH/p/6856865.html
Copyright © 2011-2022 走看看