2565: 最长双回文串
Description
顺序和逆序读起来完全一样的串叫做回文串。比如
acbca
是回文串,而
abc
不是(
abc
的顺序为
“abc”
,逆序为
“cba”
,不相同)。
输入长度为 n 的串 S ,求 S 的最长双回文子串 T, 即可将 T 分为两部分 X , Y ,( |X|,|Y|≥1 )且 X 和 Y 都是回文串。
输入长度为 n 的串 S ,求 S 的最长双回文子串 T, 即可将 T 分为两部分 X , Y ,( |X|,|Y|≥1 )且 X 和 Y 都是回文串。
Input
一行由小写英文字母组成的字符串S。
Output
一行一个整数,表示最长双回文子串的长度。
Sample Input
baacaabbacabb
Sample Output
12
HINT
样例说明
从第二个字符开始的字符串aacaabbacabb可分为aacaa与bbacabb两部分,且两者都是回文串。
对于100%的数据,2≤|S|≤10^5
从第二个字符开始的字符串aacaabbacabb可分为aacaa与bbacabb两部分,且两者都是回文串。
对于100%的数据,2≤|S|≤10^5
分析:
其实就是快速求解以下两个数组:
L[I]表示s[1] s[2]......s[i]的最大回文后缀 =>这一点恰恰是回文树的功能之一。
R[i]表示s[i] s[i+1]......s[n]的最长回文前缀
那么所求的答案就是 L[I]+R[i+1]的最大值 ,其中(1<=i<=n-1)。
怎么快速求出R[i]呢?
其实在向回文树种添加第i个字母,沿着后缀边走的时候,一旦匹配,将当前讨论的回文串的起点的 前一个字母记为p,显然s[p]s[p+1]...s[i]是以p开头更长的一个回文串,就可以更新R[p]了。
注意操作中有两次匹配过程,都要更新R[p]。
总用时 : 60ms
代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100000+5;
struct node{
int len,suffix;
int Next[26];
};
node tree[maxn];
int max_suffix,tot; //max_suffix指向当前最长的后缀回文串。
char s[maxn];
int n,l[maxn],r[maxn],ans=0;
int idx(char x){return x-'a';}
void init(){
//初始化,添加两个节点。
tot=max_suffix=2;
tree[1].len=-1; //根节点1,长度为-1,可以向下构成单字母的串
tree[2].len=0; //根节点2,长度为0。
tree[2].suffix=tree[1].suffix=1;
}
bool add_letter(int pos){
int cur=max_suffix,curlen=0,id=idx(s[pos]);
while(true){ //顺着后缀边走,找到 s[pos]+回文串+s[pos] 的形式
curlen=tree[cur].len;
if(pos-1-curlen>0&&s[pos-1-curlen]==s[pos]){
r[pos-curlen-1]=curlen+2; //更新R[i]
break;
}
cur=tree[cur].suffix;
}
if(tree[cur].Next[id]){ //已经存在
max_suffix=tree[cur].Next[id];
return false;
}
//添加新节点
max_suffix= ++tot;
tree[cur].Next[id]= tot;
tree[tot].len=curlen+2;
//处理新节点的suffix边
if(tree[tot].len==1){
tree[tot].suffix=2; // 单独的字符,后缀边指向空串
return true;
}
while(true){ //再次查找
cur=tree[cur].suffix;
curlen=tree[cur].len;
if(pos-1-curlen>0&&s[pos-1-curlen]==s[pos]){
tree[tot].suffix=tree[cur].Next[id];
r[pos-curlen-1]=curlen+2; //更新R[i]
break;
}
}
return true;
}
int main(){
int i;
scanf("%s",s+1);
n=strlen(s+1);
init();
for(i=1;i<=n;i++){
add_letter(i);
l[i]=tree[max_suffix].len;
}
for(i=1;i<n;i++)ans=max(ans,l[i]+r[i+1]);
if(ans>1)printf("%d",ans);
else puts("0");
return 0;
}
当然,还有一种方法:将字符串倒过来建一遍树就可以求出R[i]了。
但是由于两次建树,时间效率会比上面一种方法略低。 用时:192 ms
代码实现上有一点不同,以下的代码将更具备移植性:
#include<cstdio>
#include<iostream>
#include<cstring>
#define CLEAR(xxx) memset(xxx, 0, sizeof(xxx))
using namespace std;
const int maxn=100000+5;
struct node{
int len,suffix;
int Next[26];
};
node tree[maxn];
int max_suffix,tot,len; //max_suffix指向当前最长的后缀回文串。
char s[maxn],S[maxn];
int n,l[maxn],r[maxn],ans=0;
int idx(char x){return x-'a';}
void init(){
//初始化,添加两个节点。
for(int i=1;i<=n;i++){
tree[i].len=tree[i].suffix=0;
CLEAR(tree[i].Next);
}
memset(s,0,sizeof(s));
tot=max_suffix=2; len=0;
tree[1].len=-1; //根节点1,长度为-1,可以向下构成单字母的串
tree[2].len=0; //根节点2,长度为0。
tree[2].suffix=tree[1].suffix=1;
}
int Match(int x){ //匹配操作
while(s[len]!=s[len-tree[x].len-1]) x=tree[x].suffix;
return x;
}
bool add_letter(char x){
s[++len]=x;
int cur,id=idx(x);
cur=Match(max_suffix);
//顺着后缀边走,找到 s[pos]+回文串+s[pos] 的形式
if(tree[cur].Next[id]){ //已经存在
max_suffix=tree[cur].Next[id];
return false;
}
//添加新节点
max_suffix= ++tot;
tree[cur].Next[id]= tot;
tree[tot].len=tree[cur].len+2;
//处理新节点的suffix边
if(tree[tot].len==1){
tree[tot].suffix=2; // 单独的字符,后缀边指向空串
return true;
}
cur=Match(tree[cur].suffix); //再次查找
tree[tot].suffix=tree[cur].Next[id];
return true;
}
int main(){
int i;
scanf("%s",S+1);
n=strlen(S+1);
init();
for(i=1;i<=n;i++){
add_letter(S[i]);
l[i]=tree[max_suffix].len;
}
init();
for(i=n;i>0;i--){
add_letter(S[i]);
r[i]=tree[max_suffix].len;
}
for(i=1;i<n;i++)ans=max(ans,l[i]+r[i+1]);
if(ans>1)printf("%d",ans);
else puts("0");
return 0;
}