BZOJ 2565 最长双回文串 哈希+二分+线扫+树状数组

Description

顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分XY,(|X|,|Y|≥1)且XY都是回文串。

Input

一行由小写英文字母组成的字符串S

Output

一行一个整数,表示最长双回文子串的长度。

Sample Input

baacaabbacabb

Sample Output

12

HINT

样例说明

从第二个字符开始的字符串aacaabbacabb可分为aacaa与bbacabb两部分,且两者都是回文串。

对于100%的数据,2≤|S|≤10^5


2015.4.25新加数据一组





网上都是用manacher搞回文串的……可是我不会啊= =
求回文串可以把串反过来然后哈希,
接着二分一个长度,判断两段是否相等即可判断回文。
处理奇偶性的问题,可以间隔加入'{'
比如aaaa,就变成{a{a{a{a{
那么都是奇数了。

预处理出所有的最长的回文串,然后枚举断点。
假如一个断点pos,(pos-1)及左边是左边的回文串,pos及右边是右边的回文串,
加入断点pos的左边有某个回文串的mid,
根据回文串的特性,比如aaaaa,1~5,2~4,3~3都是回文串
所以事实上只需要保存以一个点为中点的最长回文串就够了,
然后如果有下面的关系(某一个回文串左端点是l,右是r,中点是mid):
那么(mid-(pos-mid))~pos也是一个回文串,
根据这个性质,也就是说我们需要维护两个:
1.所有r>=pos的mid在pos左边的最小mid;
2.所有l<=pos的mid在pos右边的最大mid。
对于1,只要把所有得出的回文串按照r从大到小排序,
然后从后往前枚举pos,并且用一个单调的指针维护1中的最小mid;
(单调指针,就是因为r有序,所以每次当指针point,r[point]>=pos,就point++,
因为都是从大到小的,所以point只会单调……)
对于2似乎没什么好的办法,但是看到是l<pos的最大mid,
就可以考虑到树状数组了。
在指针point移到某一个回文串,就在树状数组里更新维护2的最大mid。
同时在每个pos统计答案即可。

不是很复杂的想法但是还是有一点细节。
主要是奇偶性,以及转为奇数串、知道最小最大mid后的答案计算方法。
具体可以看代码……

方法比较麻烦,但是时间还是主要在hash上,,
manacher比较优越。



#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int 
	seed=1311,Mod=234065077,
	N=200005;
int n,len,tr[N];
int fac[N],hash[2][N];
char s[N];
struct HuiWen{int l,mid,r;}hw[N];
bool cmp(HuiWen x,HuiWen y){return x.r>y.r;}
void update(int x,int y){
	for (;x<=len;x+=x&-x)
		if (hw[tr[x]].mid<hw[y].mid) tr[x]=y;
}
int get(int x){
	int y=0;
	for (;x;x-=x&-x)
		if (hw[y].mid<hw[tr[x]].mid) y=tr[x];
	return y;
}
void HASH(char s[],int opt){
	hash[opt][0]=0,fac[0]=1;
	for (int i=1;i<=len;i++)
		fac[i]=(ll)fac[i-1]*seed%Mod;
	for (int i=1;i<=len;i++)
		hash[opt][i]=((ll)hash[opt][i-1]*seed%Mod+s[i]-'a')%Mod;
}
int HASHNUM(int opt,int x,int y){
	int t1=(ll)hash[opt][x-1]*fac[y-x+1]%Mod,
		t2=hash[opt][y];
	return (t2-t1+Mod)%Mod;
}
int BS(int m){
	int l=0,r=min(len-m,m-1),mid;
	while (l<r){
		mid=(l+r+1)>>1;
		if (HASHNUM(0,m-mid,m-1)==HASHNUM(1,len-(m+mid)+1,len-m))
			l=mid; else r=mid-1;
	}
	return l;
}
void Pre(){
	char s1[N<<1];
	for (int i=1;i<=len;i++) s1[i]=s[i];
	for (int i=1;i<=len;i++) s[(i<<1)-1]='{',s[i<<1]=s1[i];
	len=len<<1|1,s[len]='{';
	for (int i=1;i<=len;i++) s1[i]=s[len-i+1];
	HASH(s,0),HASH(s1,1);
	n=0;
	for (int i=1;i<=len;i++){
		int t=BS(i);
		hw[++n].l=i-t,hw[n].r=i+t,hw[n].mid=i;
	}
}
void solve(){
	sort(hw+1,hw+1+n,cmp);
	int MINmid=N+1,MINI=0,pj=1,ans=0;
	for (int i=len;i;i--){
		if (s[i]!='{') continue;
		while (pj<=n && hw[pj].r>=i-1){
			update(hw[pj].l,pj);
			if (MINmid>hw[pj].mid)
				MINmid=min(MINmid,hw[pj].mid),MINI=pj;
			pj++;
		}
		int t=get(i),t1=0,t2=0;
		if (hw[t].mid>=i && MINmid<=i-1){
			t1=(hw[t].mid-i)>>1;
			if (!(hw[t].mid&1)) t1++,t1=(t1<<1)-1; else t1<<=1; 
			t2=(i-MINmid)>>1;
			if (!(MINmid&1)) t2++,t2=(t2<<1)-1; else t2<<=1;
		}
		ans=max(ans,t1+t2);
	}
	printf("%d\n",ans);
}
int main(){
	scanf("%s",s+1);
	len=strlen(s+1);
	Pre();
	solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值