原题地址
http://codeup.hustoj.com/problem.php?cid=100000629&pid=0
题目大意
输入一个字符串,求出其中最长的回文子串。在判断回文时,应该忽略所有标点符号和空格,且忽略大小写,但输出应保持原样(在回文串的首部和尾部不要输出多余字符)。输入字符串长度不超过 5000 5000 5000,且占据单独的一行。应该输出最长的回文串,如果有多个,输出起始位置最靠左的。
解题思路
一开始用dp的方法做,告诉我空间超限了,一看诶长度最大是 5000 5000 5000,而用dp的时候开的数组 d p [ m a x n ] [ m a x n ] dp[maxn][maxn] dp[maxn][maxn] 看来太大了,所以要换个办法~
后来去学了一下传说中找回文子串复杂度最优的算法——Manacher算法,复杂度为 O ( n ) O(n) O(n),AC本题。
找到必须看的一篇博客(俺也讲不清楚qwq):
必看!这篇看懂了!!hdu3068之manacher算法+详解
细节处理:读入字符串为 a l l all all,处理成全为小写字母且不带标点的 s s s,处理过程中用 p o s pos pos 数组记录 s s s 中每个字符对应在 a l l all all 中的位置,之后输出时知道 s s s 的左右端点就可以对应好位置,去 a l l all all 输出了。
注意事项
1.下标的转化。如经过Manacher算法预处理之后的字符串 s s s 长度是变长了,记录最长回文子串左端点的 l e f t left left 对应到预处理之前的 s s s,下标应该是 l e f t − 1 2 \frac {left-1}{2} 2left−1。
2.注意 p [ i ] p[i] p[i] 数组代表以 i i i 为中心的最长回文半径。因为是半径,所以可以算出子串的长度就是 p [ i ] − 1 p[i] - 1 p[i]−1(需要认真阅读上面的博文,理解算法构造的原理之后自己推一下)。
参考代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for(int i = (a); i < (b); ++i)
#define _rep(i, a, b) for(int i = (a); i <= (b); ++i)
#define pb push_back
#define LOCAL //提交的时候一定注释
#define INF 0x3f3f3f3f
const int maxn = 6000;
int pos[maxn], p[maxn * 2];
int readint() {
int x; scanf("%d", &x); return x;
}
int main() {
#ifdef LOCAL
freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
string all, s = "";
getline(cin, all);
_for(i, 0, all.size()) {
if (isalpha(all[i])) {
pos[s.size()] = i;
s += tolower(all[i]);
}
}
int len = s.size(), ans = 0, id = 0, left; //left记录左端点的值
s += string(len + 4, '0');
for (int i = len; i >= 0; --i) {
s[i + i + 2] = s[i];
s[i + i + 1] = '#';
}
s[0] = '*';
_rep(i, 2, 2 * len) {
if (p[id] + id > i)
p[i] = min(p[2*id-i], p[id] + id - i);
else p[i] = 1;
while (s[i - p[i]] == s[i + p[i]]) ++p[i];
if (p[i] + i > p[id] + id) id = i;
if (p[i] > ans) {
left = i - p[i] + 1; //注意这里p[i]表示的是回文半径
ans = p[i] ;
}
}
_rep(i, pos[(left-1)/2], pos[(left-1)/2 + ans - 2]) printf("%c", all[i]);
return 0;
}