回文树 BZOJ 2565 最长双回文串

24 篇文章 0 订阅
16 篇文章 0 订阅

2565: 最长双回文串

Description

顺序和逆序读起来完全一样的串叫做回文串。比如 acbca 是回文串,而 abc 不是( abc 的顺序为 “abc” ,逆序为 “cba” ,不相同)。
输入长度为 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

分析:
其实就是快速求解以下两个数组:
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;  
}  


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值