[APIO2014]回文串

本博客是写给那些没学过SAM(因为博主自己也没学过),想用Manacher+后缀数组做这道题的人的

题目大意

定义一个子串的价值为它在原串中出现的次数与它的长度的乘积,求最大回文子串的价值

思路

首先我们上一个马拉车,求出所有本质不同的回文子串。你也许会问,它们的数量难道不会爆int吗?事实上,它们最多只有\(\lvert S \rvert\)个(S为原串),证明如下:
考虑数学归纳法,令\(n\)为原串的长度
1.\(n=1\)时,显然成立
2.若\(n=k\)时成立,来证明\(n=k+1\)时成立。考虑在长度为\(n\)的串后加上一个字符,则本质不同的回文串最多增加一个。反证,假设有多个,则一定有一个最长的,而且它们都包含新增的那个字符。故所有的比它短的新增回文子串都可以沿它的对称轴对称过去,得到一个和它一样的回文子串,且对称过去得到的子串一定也是原来长度为\(n\)的串的子串,与本质不同矛盾。故最多增加一个,所以\(n=k+1\)时也成立
3.故对于所有正整数\(n\),结论都成立
那怎么用Manacher来\(O(n)\)地求出所有本质不同的回文子串呢?在算法中我们记录了一个表示所有回文子串最远能扩展到的位置\(pos\)。若当前位置的回文子串包含于\(pos\)中,表明一个跟他一毛一样的串在之前已经出现过了,否则,也就是\(i+f[i]>pos\)时,表明出现了一个本质不同的回文子串,我们统计一下它的贡献就好了。
要求一个子串的出现次数,我们可以在\(height\)数组上ST表+二分,然后就没啦!

#include <bits/stdc++.h>

using namespace std;

#define N 500000

struct S {
    int x, a[2];
}v[N+5], tmp[N+5];

char s[4*N+5];
int n, f[4*N+5], sa[N+5], rk[N+5], height[N+5], sum[N+5], st[N+5][30];
long long ans = 0;

int radixSort(int sz) { //sz:字符集大小
    for(int j = 0; j <= 1; ++j) {
        memset(sum, 0, sizeof sum);
        for(int i = 1; i <= n; ++i) sum[v[i].a[j]]++;
        for(int i = 1; i <= sz; ++i) sum[i] += sum[i-1];
        for(int i = n; i >= 1; --i) tmp[sum[v[i].a[j]]--] = v[i];
        for(int i = 1; i <= n; ++i) v[i] = tmp[i];
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i)
        if(v[i].a[0]==v[i-1].a[0] && v[i].a[1]==v[i-1].a[1]) rk[v[i].x] = cnt;
        else rk[v[i].x] = ++cnt;
    return cnt;
}

void buildSA() {
    for(int i = 1; i <= n; ++i) v[i].x = i, v[i].a[0] = 0, v[i].a[1] = s[i];
    int cnt = radixSort(255);
    for(int l = 1; cnt < n; l <<= 1) {
        for(int i = 1; i <= n; ++i) v[i].x = i, v[i].a[0] = i+l>n?0:rk[i+l], v[i].a[1] = rk[i];
        cnt = radixSort(cnt);
    }
    for(int i = 1; i <= n; ++i) sa[rk[i]] = i;
    for(int i = 1, b = 0, c; i <= n; height[rk[i++]] = b) //注意height和原数组是错位的
        for(b ? --b : 0, c = sa[rk[i]-1]; s[i+b] == s[c+b]; ++b);
}

int Log2[N+5];
void buildST() { //构建ST表 
    for(int i = 1; i <= n; ++i) st[i][0] = height[i];
    for(int i = 2; i <= n; ++i) Log2[i] = Log2[i>>1]+1;
    for(int j = 1; (1<<j) <= n; ++j)
        for(int i = 1; i+(1<<j)-1 <= n; ++i)
            st[i][j] = min(st[i][j-1], st[i+(1<<j-1)][j-1]);
}

int query(int L, int R) { //LCP
    int x = Log2[R-L+1];
    return min(st[L][x], st[R-(1<<x)+1][x]);
}

void calc(int l, int r) {
    int x = rk[l+1>>1], len = (r>>1)-(l+1>>1)+1; //回到原串中的位置 
    int cnt = 1;
    int L = 1, R = x, mid, t = R;
    while(L <= R) { //向前二分 
        mid = L+R>>1;
        if(query(mid, x) >= len) R = mid-1, t = mid;
        else L = mid+1;
    }
    if(height[t] >= len) cnt += x-t+1;
    L = x+1, R = n, t = L;
    while(L <= R) { //向后二分 
        mid = L+R>>1;
        if(query(x+1, mid) >= len) L = mid+1, t = mid;
        else R = mid-1;
    }
    if(height[t] >= len) cnt += t-x;
    ans = max(ans, 1LL*len*cnt);
}

void Manacher() {
    s[2*n+1] = '#';
    for(int i = n; i >= 1; --i) s[2*i] = s[i], s[2*i-1] = '#'; //不要忘了统计长度为偶数的回文子串
    int mx = 0, pos = 0;
    for(int i = 1; i < 2*n+1; ++i) {
        if(mx > i) f[i] = min(f[2*pos-i], mx-i);
        else f[i] = 1;
        while(i-f[i] && s[i-f[i]] == s[i+f[i]]) {
            f[i]++;
            if(i+f[i] > mx) calc(i-f[i]+1, i+f[i]-1); //发现了一个本质不同的回文子串 
        }
        if(i+f[i] > mx) mx = i+f[i], pos = i; //更新边界 
    }
}

int main() {
    scanf("%s", s+1);
    n = strlen(s+1);
    buildSA();
    buildST();
    Manacher();
    printf("%lld\n", ans);
    return 0;
}
posted @ 2019-01-24 09:16  dummyummy  阅读(219)  评论(0编辑  收藏  举报