bzoj2565: 最长双回文串(manacher)
题目描述:顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串。
输入格式:一行由小写英文字母组成的字符串S。
输出格式:一行一个整数,表示最长双回文子串的长度。
样例输入:
baacaabbacabb
样例输出:
12
解析:看到回文串似乎就会想到manacher。
首先跑一遍manacher。
关键是接下来怎么做,如果强行枚举断点会变成\(O(n^2)\)。
设l[i]表示右端点为i的回文串的最长长度,r[i]表示左端点为i的回文串的最长长度。
那么可以在manacher的同时求出l[i]和r[i]的初值。
l[i + hw[i] - 1] = max(l[i + hw[i] - 1], hw[i] - 1)
r[i - hw[i] + 1] = max(r[i - hw[i] + 1], hw[i] - 1)
最后再递推求出最终的l[i]和r[i]
l[i] = max(l[i], l[i + 2] - 2)
r[i] = max(r[i], r[i - 2] - 2)
最后枚举每一个添加进去的字符,答案即为max{l[i] + r[i]}。
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 5;
int n, hw[maxn << 1], ans, l[maxn << 1], r[maxn << 1];
char s[maxn << 1];
void prepare(void) {
s[n << 1] = '#';
for (int i = n; i ; -- i) {
s[i * 2 - 1] = s[i];
s[i * 2 - 2] = '#';
}
n <<= 1;
}
void manacher(void) {
prepare();
int mid = 0, mr = 0;
for (int i = 1; i <= n; ++ i) {
if (i < mr) hw[i] = min(hw[mid * 2 - i], mr - i);
else hw[i] = 1;
while (i - hw[i] >= 0 && i + hw[i] <= n && s[i - hw[i]] == s[i + hw[i]]) hw[i] ++;
if (i + hw[i] - 1 > mr) {
mr = i + hw[i] - 1;
mid = i;
}
}
}
int main() {
scanf("%s", s + 1); n = strlen(s + 1);
manacher();
for (int i = 1; i <= n; ++ i) { //处理出初值
l[i + hw[i] - 1] = max(l[i + hw[i] - 1], hw[i] - 1);
r[i - hw[i] + 1] = max(r[i - hw[i] + 1], hw[i] - 1);
}
for (int i = 0; i <= n; i += 2) r[i] = max(r[i], r[i - 2] - 2); //递推求出l[i]和r[i]
for (int i = n; i >= 0; i -= 2) l[i] = max(l[i], l[i + 2] - 2);
for (int i = 0; i <= n; i += 2) //注意l[i]和r[i]都不能为0!
if (l[i] && r[i]) ans = max(ans, l[i] + r[i]);
printf("%d", ans);
return 0;
}