首先做这个题目时候以为是动态规划,可惜的是超时了。虽然超时了,但还是先说说动态规划。设原串为s用DP[i,j]表示s[i...j]中最大回文字串的长度,则DP[i,j]的子问题可以划分为DP[i+1,j],DP[i,j-1],以及DP[i+1,j-1].当然还需要一个数组flag[i,j]来记录状态。若flag[i,j]=1,则它表示在 s[i,j]中两个端点字符s[i】与s[j] 是s[i..j]最长回文子串构成的部分,即s[i,j]就是一个回文串。否则flag[i,j]=0表示s[i]和s[j]不同时是s[i..j]子串的构成部分。状态转移方程可以如下:
若flag[i ][j]==0 or s[i]!=s[j] ,DP[i,j]=max{ DP[i+1,j],DP[i,j-1]},若flag[i][j]==1 and s[i]==s[j] ,DP[i,j]=DP[i-1][j-1]+2.
具体处理时,不用开一个很大的数组,因为打表时候用到的只有那几行,定义DP[3][MAX_SIZE]即可,计算时采用取模运算,详见代码。很可惜,这是O(n^2)的算法AC不了。
#include<iostream>
#include<string>
using namespace std;
#define MAX_SIZE 110004
#define n 3
int DP[n][MAX_SIZE];
bool flag[n][MAX_SIZE];
char s[MAX_SIZE];
int The_Longest_Palindrome();
int MAX(int a, int b){
return a > b ? a : b;
}
int main(){
int ans;
while (cin>>s){
ans = The_Longest_Palindrome();
printf("%d\n", ans);
}
return 0;
}
int The_Longest_Palindrome(){
int l, L, i, j;
L = strlen(s);
for (j = 1; j <= L + 1; j++){
DP[j%n][j] = 1; //初始化
DP[j%n][j - 1] = 0;
flag[j%n][j] = flag[j%n][j-1] = 1;
}
DP[0][0] = 1;
for (l = 2; l <= L; l++){ //长度
for (i = 0; i <= L - l; i++){
j = i + l - 1; //计算出 j 的位置
if (s[i] == s[j] && flag[(i + 1) % n][j - 1]){
DP[i%n][j] = 2 + DP[(i + 1) % n][j - 1];
flag[i%n][j] = 1;
}
else{
DP[i%n][j] = MAX(DP[(i + 1) % n][j], DP[i%n][j - 1]); //子序列
flag[i%n][j] = 0;
}
}
}
return DP[0][L - 1];
}
下面用到的算法参考博文:Manacher算法总结 - OPEN 开发经验库http://www.open-open.com/lib/view/open1419150233417.html
叫做Manacher算法。
定义p[i]表示以第i个字符为中心的最大回文串的半径长度(包括第 i 个字符)。计算的时候从前向后扫描一遍,也就是说在计算p[i+k]的时候p[0...i+k-1]都是计算好了的。假设 i+p[i] 是在已经计算完的序列中能延伸到的最右端。我们下一步计算p[i+k],那么点i+k必定包含在p[i]+i中,即为i+k<=i+p[i] => k<=p[i] ,由对称性可知。下面的讨论分为3个情况:
1)i-k位置的回文串完全包含在i位置的回文串内部,i-k回文串最左端位置为:i-k-p[i-k],而 i 回文串最左端为; i-p[i] 。这时候 i 回文串在i-k回文串左端的左端,即有 i-p[i]<i-k-p[i-k] => p[i]>k+p[i-k]. 对称可知,i+k的回文串至少有 一段和i-k的回文串相同 。倘若i+k的回文串向两边延伸,势必会得到 i-k 的串也会增长,从而i+k的串不可能再向两边延伸。故而有:
若p[i]>k+p[i-k] 则:
p[i+k]=p[i-k];
2) i-k位置的回文串左端 恰好与 i 位置的回文串左端重合。此时有i-k-p[i-k]=i-p[i] => p[i]=k+p[i-k].这个时候至少有 p[i+k]=p[i-k],但是此时i+k串向两边延伸是合法的,不会产生矛盾。故而有:
若p[i]=k+p[i-k]则:
p[i+k]=p[i-k];
while(s[i+k+p[i+k]]==s[i+k-p[i+k]]) p[i+k]++;
3)i-k位置的回文串最左端超过了 i 位置的回文串,即有 i-k-p[i-k]<i-p[i] =>p[i]<p[i-k]+k .此时由于 i 回文串对称性可知 至少有 i+k的回文串与i-k的回文串在 i 回文串内部的那部分相等,即至少有 p[i+k]=p[i]-k,倘若i+k的回文串可以向两边延伸,那么势必会让 i 的回文串也增长吗,这样与i的回文串矛盾。故而有;
若 p[i]<k+p[i-k]则:
p[i+k]=p[i]-k;
以上三种情况综合起来考虑就是:
p[i+k]=min{p[i]-k,p[i-k]} ;
while(s[i+k+p[i+k]]==s[i+k-p[i+k]]) p[i+k]++;
具体处理的时候将长度为len的串每个字符和两端都插上一个字符‘#’,或者其它串中没有出现的字符。此时串长度变为2*len+1.
#include<iostream>
using namespace std;
char ch[110003 * 2];
int p[110003 * 2];
int Min(int a, int b){
return a < b ? a : b;
}
int main(){
int i, len, k, maxlen;
p[0] = 1, p[1] = 2;
while (cin>>ch){
len = strlen(ch);
for (i = len - 1; i >= 0; i--){
ch[2 * i + 2] = '#';
ch[2 * i + 1] = ch[i];
}
ch[0] = '#';
//----------------------------------------完成初始化插值
len = len * 2 + 1; //新数组长度
i = k = 1;
maxlen = 2;
while (i + k < len){
p[i + k] = Min(p[i - k], p[i] - k); //计算p[i+k]
while (i + k >= p[i + k] && ch[i + k + p[i + k]] == ch[i + k - p[i + k]])
p[i + k]++; //将p[i+k]向两边延伸
if (maxlen < p[i + k])
maxlen = p[i + k]; //更新maxlen
if (p[i + k] + i + k >= p[i] + i){ //更新 i,还原k
i += k;
k = 1;
}
else
k++; //更新k
}
printf("%d\n", maxlen - 1);
}
return 0;
}