[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;
}