NOI 2016 优秀的拆分 后缀数组

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

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

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

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

以下事项需要注意:

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

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

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

思路:

这题很久以前就想做了(当时写了个暴力就跑了),这几天复习后缀数组就正好拿来做一做。

这题的重点其实就是统计字符串中形如AA的字符串个数,还是有不小思维难度的~~~

首先枚举A的长度l,再枚举一个k,i = k * l, j = (k + 1) * l,设后缀 i 和后缀 j 的LCP为x,前缀 i - 1 和前缀 j - 1 的最长公共后缀为y,则当x + y = l时,我们恰能找到[i - y, j + x - 1]为一个合法的AA串;那么当x + y > l 时,我们就能找到一个合法的串区间(有不少细节,建议自己推一推)。用差分序列加上答案,最后做前缀和就能得到每一个位置起始以及终止的AA串个数,答案就很好统计了。

另外数组注意清空,最后由于这个WA了几次(当然这与后缀数组的写法有关)。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

#define For(i,j,k) for(int i = j;i <= k;i++)
#define Forr(i,j,k) for(int i = j;i >= k;i--)
#define Set(i,j) memset(i, j, sizeof(i))

using namespace std;

const int N = 30010;

int n, Log[N], pre[N], suf[N];

struct SuffixArray{
    int sa[N<<1], rank[N], h[N][18], T1[N], T2[N], c[N];
    char S[N];

    void init(){
        Set(S, 0), Set(c, 0), Set(T1, 0), Set(T2, 0);
        Set(h, 0), Set(sa, 0), Set(rank, 0);
    }

    void buildsa(int m = 'z'){
        int *x = T1, *y = T2;
        For(i,1,n) c[x[i] = S[i]]++;
        For(i,1,m) c[i] += c[i-1];
        Forr(i,n,1) sa[c[x[i]]--] = i;
        for(int k = 1;k < n;k <<= 1){
            int p = 0;
            For(i,n-k+1,n) y[++p] = i;
            For(i,1,n) if(sa[i] > k) y[++p] = sa[i] - k;
            For(i,1,m) c[i] = 0;
            For(i,1,n) c[x[y[i]]]++;
            For(i,1,m) c[i] += c[i-1];
            Forr(i,n,1) sa[c[x[y[i]]]--] = y[i];
            swap(x, y);
            p = x[sa[1]] = 1;
            For(i,2,n) x[sa[i]] = y[sa[i]] == y[sa[i-1]] && y[sa[i] + k] == y[sa[i-1] + k] ? p : ++p;
            if(p >= n) break;
            m = p;
        }
    }

    void buildheight(){
        int p = 0;
        For(i,1,n) rank[sa[i]] = i;
        For(i,1,n){
            int j = sa[rank[i] + 1];
            if(p) p--;
            if(!j) continue;
            while(S[i + p] == S[j + p]) ++p;
            h[rank[i]][0] = p;
        }
        For(j,1,16)
            for(int i = 1;i + (1 << j) <= n;i++) h[i][j] = min(h[i][j-1], h[i + (1 << (j - 1))][j-1]);
    }

    int LCP(int x, int y){
        x = rank[x], y = rank[y];
        if(x > y) swap(x, y);
        int t = Log[y - x];
        return min(h[x][t], h[y - (1 << t)][t]);
    }

}A, B;

int main(){
    For(i,2,N-1) Log[i] = Log[i >> 1] + 1;
    int T;
    scanf("%d", &T);
    while(T--){
        A.init(), B.init();
        Set(pre, 0), Set(suf, 0);
        scanf("%s", A.S + 1);
        n = strlen(A.S + 1);
        A.buildsa();
        For(i,1,n) B.S[i] = A.S[n + 1 - i];
        B.buildsa();
        A.buildheight();
        B.buildheight();
        For(l,1,n/2)
            for(int i = l, j = i + l;j <= n;i += l, j += l){
                int R = min(A.LCP(i, j), l), L = min(B.LCP(n + 1 - (i - 1), n + 1 - (j - 1)), l - 1);
                if(L + R >= l){
                    suf[j - L - l]++, suf[i + R - l + 1]--;
                    pre[j - L + l - 1]++, pre[i + R + l]--;
                } 
            }
        For(i,1,n) pre[i] += pre[i-1], suf[i] += suf[i-1];
        long long Ans = 0;
        For(i,1,n) Ans += pre[i] * suf[i+1];
        printf("%lld\n", Ans);
    }
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值