洛谷 P1117 [NOI2016]优秀的拆分 后缀数组

题目描述

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

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

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

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

以下事项需要注意:

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

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

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

输入输出格式

输入格式:
每个输入文件包含多组数据。

输入的第一行只有一个整数 T T ,表示数据的组数。保证 1T10

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

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

输入输出样例

输入样例#1:
4
aabbbb
cccccc
aabaabaabaa
bbaabaababaaba
输出样例#1:
3
5
4
7
说明

我们用 Si,j S i , j ​ 表示字符串 S S i 个字符到第 j j 个字符的子串(从 1 开始计数)。

第一组数据中,共有 3 3 个子串存在优秀的拆分:

S1,4=aabb,优秀的拆分为 A=a A = a B=b B = b
S3,6=bbbb S 3 , 6 = b b b b ,优秀的拆分为 A=b A = b B=b B = b
S1,6=aabbbb S 1 , 6 = a a b b b b ,优秀的拆分为 A=a A = a B=bb B = b b

而剩下的子串不存在优秀的拆分,所以第一组数据的答案是 3 3

第二组数据中,有两类,总共 4 个子串存在优秀的拆分:

对于子串 S1,4=S2,5=S3,6=cccc S 1 , 4 = S 2 , 5 = S 3 , 6 = c c c c ,它们优秀的拆分相同,均为 A=c A = c B=c B = c ,但由于这些子串位置不同,因此要计算 3 3 次;

对于子串 S1,6=cccccc,它优秀的拆分有 2 2 种:A=c B=cc B = c c A=cc A = c c B=c B = c ,它们是相同子串的不同拆分,也都要计入答案。

所以第二组数据的答案是 3+2=5 3 + 2 = 5

第三组数据中, S1,8 S 1 , 8 S4,11 S 4 , 11 各有 2 2 种优秀的拆分,其中 S1,8 是问题描述中的例子,所以答案是 2+2=4 2 + 2 = 4

第四组数据中, S1,4,S6,11,S7,12,S2,11,S1,8 S 1 , 4 , S 6 , 11 , S 7 , 12 , S 2 , 11 , S 1 , 8 各有 1 1 种优秀的拆分,S3,14 2 2 种优秀的拆分,所以答案是 5+2=7

对于全部的测试点,保证 1T10 1 ≤ T ≤ 10 。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的 T T 组数据均满足限制条件。

我们假定 n 为字符串 S S 的长度,每个测试点的详细数据范围见下表:
这里写图片描述

分析:
我们设f[i]为以 i i 结尾的AA串个数, g[i] g [ i ] 为以 i i 开头的AA串个数,则

ans=i=1nf[i]g[i+1] a n s = ∑ i = 1 n f [ i ] ∗ g [ i + 1 ]

考虑怎样求 f f g,我们可以枚举 AA A A 串的一半 len l e n ,然后每隔 len l e n 设一个断点,那么一个长度为 2len 2 ∗ l e n AA A A 必过两个断点。这两个断点 i i j的前缀的 lsp l s p 和这两个位置的后缀的 lcp l c p 加起来为 len l e n ,则存在一个这样的 AA A A 串;如果大于,则存在多个这样的串,而每个串可以取 lsp l s p lcp l c p 的一部分,使得最终长度为 len l e n 。这个直接后缀数组搞掉即可。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long

const int maxn=2e5+7;

using namespace std;

int T,n;
int c[maxn],x[maxn],y[maxn];
LL f[maxn],g[maxn],ans;

struct suffix_array{
    char s[maxn];
    int rank[maxn],sa[maxn];
    int h[maxn][15];
    void getsa()
    {
        memset(rank,0,sizeof(rank));
        memset(sa,0,sizeof(sa));
        memset(h,0,sizeof(h));
        memset(c,0,sizeof(c));
        memset(x,0,sizeof(x));
        memset(y,0,sizeof(y));
        int m=1000;
        for (int i=1;i<=m;i++) c[i]=0;
        for (int i=1;i<=n;i++) x[i]=s[i];
        for (int i=1;i<=n;i++) c[x[i]]++;
        for (int i=1;i<=m;i++) c[i]+=c[i-1];
        for (int i=n;i>0;i--) sa[c[x[i]]--]=i;
        for (int k=1;k<=n;k<<=1)
        {
            int num=0;
            for (int i=n-k+1;i<=n;i++) y[++num]=i;
            for (int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
            for (int i=1;i<=m;i++) c[i]=0;
            for (int i=1;i<=n;i++) c[x[i]]++;
            for (int i=1;i<=m;i++) c[i]+=c[i-1];
            for (int i=n;i>0;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            num=1;
            x[sa[1]]=1;
            for (int i=2;i<=n;i++)
            {
                if ((y[sa[i]]!=y[sa[i-1]]) || (y[sa[i]+k]!=y[sa[i-1]+k]))
                {
                    x[sa[i]]=++num;
                }
                else x[sa[i]]=num;
            }
            if (num>=n) break;
            m=num;
        }
        for (int i=1;i<=n;i++) rank[i]=x[i];
    }
    void getheight()
    {
        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++;
            h[rank[i]][0]=k;
        }
        int c=1;
        for (int j=1;j<15;j++)
        {
            for (int i=1;i<=n;i++)
            {
                h[i][j]=min(h[i][j-1],h[i+c][j-1]);
            }
            c<<=1;
        }
    }
    int lcp(int x,int y)
    {
        x=rank[x],y=rank[y];
        if (x>y) swap(x,y);
        x++;
        int k=trunc(log(y-x+1.5)/log(2));
        return min(h[x][k],h[y-(1<<k)+1][k]);
    }
}A,B;

int main()
{
    scanf("%d",&T);
    while (T--)
    {   
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        scanf("%s",A.s+1);
        n=strlen(A.s+1);        
        for (int i=1;i<=n;i++) B.s[n-i+1]=A.s[i];       
        A.getsa(),A.getheight();
        B.getsa(),B.getheight();                                    
        for (int len=1;len<=n/2;len++)
        {
            for (int i=len,j=i+len;j<=n;i+=len,j+=len)
            {
                int x=min(A.lcp(i+1,j+1),len-1),y=min(B.lcp(n-i+1,n-j+1),len);
                int t=x+y-len+1;
                if (t>0)
                {
                    g[i-y+1]++,g[i-y+1+t]--;
                    f[j+x+1-t]++,f[j+x+1]--;
                }
            }
        }                               
        for (int i=1;i<=n;i++) f[i]+=f[i-1],g[i]+=g[i-1];
        ans=0;                              
        for (int i=1;i<=n;i++) ans+=f[i]*g[i+1];
        printf("%lld\n",ans);
    }
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值