http://acm.hdu.edu.cn/showproblem.php?pid=5340
题意:问是否能将原串分解成三个回文的非空字符串。
思路:用Manmcher算法先求出原串中所有的回文字串,保存在pa[i] 里面,pa[i] 表示以 i 为中点的回文字符串向左右延伸的最长长度,开两个数组pre[i], suf[i] 分别表示 1- i , i - n能否形成回文串,上面都能在O(n)的时间求出,然后就是枚举中间的回文字符串的中点 i ,然后就是判断pre[i-pa[i]…i-1] 和 suf[i+1…i+pa[i]] 能否同时为真。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 20010;
int pa[maxn<<1]; //以 i 为中点的最长回文子串的长度
char temp[maxn], str[maxn<<1]; //输入字符串和处理后的字符串
bool pre[maxn<<1], suf[maxn<<1]; //1-i是否回文,i-n是否回文
void Manacher(int len, char *str, int *pa) //Manacher求出所有的回文子串
{
int id, maxlen = 0;
for(int i = 0; i < len; i++){
pa[i] = 1;
if(maxlen > i)
pa[i] = maxlen - i < pa[2*id-i] ? maxlen - i : pa[2*id-i];
while(str[i+pa[i]] == str[i-pa[i]]){
pa[i]++;
}
if(i + pa[i] > maxlen){
id = i;
maxlen = i + pa[i];
}
}
}
//枚举第二个回文子串的中点
bool solve(int len, char *str, int *pa, bool *pre, bool *suf)
{
for(int i = 4;i < len - 3;i++) //枚举第二个子串中点
{
//因为第一个和第三子串都是'#'结尾所以第一个子串的结尾和第三个子串的开始都要是'#'.
for(int k = (str[i] == '#' ? 2 : 1); k <= pa[i] ; k += 2) {
if(pre[i - k]&suf[i + k] && i - k > 1 && i + k < len-1)
return true ;
}
}
return false ;
}
int main()
{
int Test;
scanf("%d", &Test);
while(Test--)
{
scanf("%s", temp);
int len = strlen(temp);
for(int i = 0; i < len; i++) //Manacher只能处理奇串,将奇串和偶串都处理成奇串
{
str[2*i+1] = '#';
str[2*i+2] = temp[i];
}
len = 2 * len + 2;
str[0] = '$'; //预防出界,字符串从 1 开始
str[len] = str[len-1] = '#';
Manacher(len, str, pa); // manacher算法
memset(pre, false, sizeof(pre));
memset(suf, false, sizeof(suf));
for(int i = 1; i < len; i++)//求出pre 和 suf
{
if(i == pa[i])
pre[i+pa[i]-1] = true;
if(pa[i] == len - i)
suf[i-pa[i]+1] = true;
}
if(solve(len, str, pa, pre, suf))
printf("Yes\n");
else
printf("No\n");
}
return 0;
}