hihocoder1032 最长回文子串
问题描述:
所谓回文串,就是形如"ababa"这样正序和反序序列完全一样的字符串。最长回文子串问题,即是要找出一个字符串的所有子串中最长的那个回文串。
题目链接:http://hihocoder.com/problemset/problem/1032
算法分析:
考虑所有长度为奇数的子串,可以这样做:
枚举子串的中心位置 i , 求出以 str[ i ] 为中心的最长回文子串,用 f[ i ] 表示以 str[ i ] 为中心的最长回文子串长度,最后找出 f[ i ]中最大的那个,即为所求最长回文子串的长度。问题的关键在于如何求解每一个 f[ i ]。若是对每一个 i 都以 str[ i ] 为中心逐渐向两边扩展,那么最坏情况下(形如"aaaaaaaaaaaaaa")的复杂度是O(n2),所以不能这样做。那么我们应该怎样做呢?下面考虑这样一个问题:假如 str[3...7]是一个回文串,同时str[3...5]是一个回文串,那么str[5...7]是不是也一定是回文串?答案是肯定的。进而我们可以得出一个结论:f[ i ]>=f[ 2*j-i ]。这个结论成立需要满足以下条件:j<i且i在以j为中心的最长回文子串内(j+f[j]/2>=i)。另外,当f[ 2*j-i ]比较大以至于超过了f[ j ]的左边界的时候,以上结论是不对的,需要做一个修正,改为:f[ i ]>=min( f[ 2*j-i ], f[j]-2*(i-j)), 也就是说此时我们能得到的f[ i ]的最小值只能取以str[2*j-i]为中心且没有超过f[j]左边界的回文串长度。结论中的 j 代表的是满足 k<i 的使以str[ k ]为中心的回文串右边界最大的k。好了,有了以上结论,那么我们在计算f[ i ]的时候就不必从长度为1的子串开始逐渐扩展,而是可以从我们求得的最小值开始了,这样即使对于极限情况,算法也可以很快结束。
对于长度为偶数的子串,我们可以通过在字符串中所有相邻字符间插入某个相同的特殊字符来把原始字符串转换成另外一个字符串,然后利用上述算法求出所有的长度为奇数的最长回文子串,最后经过简单转换即可得到结果。
下图所示为当f[2*j-i]不是很大(没有超过f[j]的左边界)的情况:
下面是我的代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 #define MAX_LEN 1000005 6 7 char str[MAX_LEN*2], tmpstr[MAX_LEN]; 8 int f[MAX_LEN*2]; 9 10 int main() 11 { 12 int n; 13 scanf("%d", &n); 14 while(n--) 15 { 16 scanf("%s", tmpstr); 17 int res = 1; 18 int len = strlen(tmpstr); 19 for(int i=0, j=0; j<len-1; i+=2, ++j) 20 { 21 str[i] = tmpstr[j]; 22 str[i+1] = '#'; 23 } 24 str[2*len-2] = tmpstr[len-1]; 25 str[2*len-1] = '\0'; 26 len = 2*len-1; 27 28 f[0] = f[len-1] = 1; 29 int x = 0, max_r = 0; 30 31 for(int i=1; i<len-1; ++i) 32 { 33 if(x+f[x]/2>=i) 34 { 35 f[i] = min(f[2*x-i], f[x]-2*(i-x)); 36 } 37 else 38 { 39 f[i] = 1; 40 } 41 42 for(int j=f[i]/2+1; i-j>=0&&i+j<len; ++j) 43 { 44 if(str[i-j]==str[i+j]) f[i] += 2; 45 else break; 46 } 47 48 if(i+f[i]/2>max_r) 49 { 50 x = i; 51 max_r = i+f[i]/2; 52 } 53 54 } 55 56 for(int i=0; i<len; ++i) 57 { 58 if(str[i+f[i]/2]=='#') f[i] = f[i]/2; 59 else f[i] = (f[i]+1)/2; 60 if(f[i]>res) res = f[i]; 61 } 62 63 printf("%d\n", res); 64 } 65 return 0; 66 }