1. 程式人生 > >[NOI 2016]優秀的拆分

[NOI 2016]優秀的拆分

mes http strlen 字符 鏈接 long limits eight include

Description

題庫鏈接

給你一個長度為 \(n\) 的只含小寫字母的字符串 \(S\) ,計算其子串有多少優秀的拆分。

如果一個字符串能被表示成 \(AABB\) 的形式,其中 \(A,B\) 非空,那麽稱 \(AABB\) 是一個優秀的拆分。一個子串可能有多個優秀的拆分。

多組詢問,詢問組數 \(T\)

\(1\leq n\leq 30000,1\leq T\leq 10\)

Solution

會用這題的方法的話,應該不難想。

如果記 \(s_{1_i}\) 為以 \(i\) 結尾的 \(AA\) 串個數,記 \(s_{2_i}\) 為以 \(i\) 開頭的 \(BB\) 串個數。

那麽答案就是 \(\sum\limits_{i=2}^n s_{1_{i-1}}\times s_{2_i}\)

考慮如何求 \(s_1\)\(s_2\)

我們可以枚舉 \(A\) 的長度 \(l\) ,用 \(i,i+l\) 這兩個點可以將所有的長度為 \(2l\)\(AA\) 並且同一個長度為 \(2l\)\(AA\) 不會被不同的一組 \(i,i+l\) 固定。

這樣對於每一組 \(i,i+l\) ,我們設法去計算它所固定的長度為 \(2l\)\(AA\)

可以求出 \(i,i+l\) 的最長公共前綴 \(x\)\(i,i+l\) 的最長公共後綴 \(y\) ,如果 \(x+y\geq l\) ,那麽存在長度為 \(2l\)\(AA\) 經過 \(i,i+l\)

求出區間左端點 \(L=\max\{i-x+1, i-l+1\}\) ,右端點 \(R=\min\{i-l+y+1, i\}\)

顯然這一段區間都是能夠對答案產生貢獻的,差分一下即可。

下發的幾個樣例字符串長度都是遞增的...大樣例過了自信 AC ,結果因為沒清空數組 wa 成茍

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 30000+5;

int bin[35], lgn[N], t, n, m, x[N<<1], y[N<<1], c[N];
ll s1[N], s2[N];

struct SA {
    char ch[N];
    int sa[N], rk[N], height[N], f[30][N];
    void get() {
        for (int i = 1; i <= n*2; i++) x[i] = y[i] = 0;
        for (int i = 1; i <= m; i++) c[i] = 0;
        for (int i = 1; i <= n; i++) c[x[i] = ch[i]]++; 
        for (int i = 2; i <= m; i++) c[i] += c[i-1];
        for (int i = n; i >= 1; 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 = 2; i <= m; i++) c[i] += c[i-1];
            for (int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i];
            swap(x, y); x[sa[1]] = num = 1;
            for (int i = 2; i <= n; i++)
                x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k]) ? num : ++num;
            if ((m = num) == n) break;
        }
        for (int i = 1; i <= n; i++) rk[sa[i]] = i;
        for (int i = 1, k = 0; i <= n; i++) {
            if (rk[i] == 1) continue;
            if (k) --k; int j = sa[rk[i]-1];
            while (i+k <= n && j+k <= n && ch[i+k] == ch[j+k]) ++k;
            height[rk[i]] = k;
        }
    }
    void rmq() {
        int t = lgn[n];
        for (int i = 1; i <= n; i++) f[0][i] = height[i];
        for (int i = 1; i <= t; i++)
            for (int j = 1; j+bin[i-1] <= n; j++)
                f[i][j] = min(f[i-1][j], f[i-1][j+bin[i-1]]);
    }
    int lcp(int a, int b) {
        a = a > n ? 0 : rk[a], b = b > n ? 0 : rk[b];
        if (a > b) swap(a, b); ++a;
        int t = lgn[b-a+1];
        return min(f[t][a], f[t][b-bin[t]+1]);
    }
}b, f;
void work() {
    bin[0] = 1; for (int i = 1; i <= 25; i++) bin[i] = (bin[i-1]<<1);
    lgn[0] = -1; for (int i = 1; i < N; i++) lgn[i] = lgn[i>>1]+1;
    scanf("%d", &t);
    while (t--) {
        memset(s1, 0, sizeof(s1)), memset(s2 ,0, sizeof(s2));
        scanf("%s", f.ch+1); n = strlen(f.ch+1);
        for (int i = 1; i <= n; i++) b.ch[n-i+1] = f.ch[i];
        m = 255; f.get(); f.rmq(); m = 255; b.get(); b.rmq();
        for (int l = 1; l <= n; l++)
            for (int i = 1; i+l <= n; i += l) {
                int x = b.lcp(n-i+1, n-(i+l)+1), y = f.lcp(i+1, i+l+1);
                if (x+y < l) continue;
                int L = max(i-x+1, i-l+1), R = min(i-l+y+1, i);
                if (L > R) continue;
                s1[L+l*2-1]++, s1[R+l*2]--;
                s2[L]++, s2[R+1]--;
            }
        for (int i = 2; i <= n; i++) s1[i] += s1[i-1], s2[i] += s2[i-1];
        ll ans = 0;
        for (int i = 2; i <= n; i++) ans += 1ll*s1[i-1]*s2[i];
        printf("%lld\n", ans);
    }
}
int main() {work(); return 0; }

[NOI 2016]優秀的拆分