您的位置:首页 > 其它

字符串 专题训练 · 最长回文子串之Manacher算法

2015-08-16 10:48 369 查看
在字符串问题中,有一类经典的问题是求字符串中的最长回文子串,而解决这类问题的算法也很多,例如后缀树或者分治+拓展KMP。但是后缀树的极为复杂,没有实用性;分治+拓展KMP的复杂度达到了O(NlogN),并不能算是非常高效。对此,我们可以学习一种专门的算法:Manacher算法。

Manacher算法的核心可以用一句话来概括:在回文子串中找回文子串,带上这个思想应该会更好理解。

可以参看星夜永恒的blog,写的非常好,一些图也画的十分贴切。

[POJ 3974]

模板题,没什么好说的。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;

const int N=2*1e6+10;
char s
;
int n,p
,maxlen,id;
int i,j,T;

int main(){
	while (~scanf("%s",s)){
		T++;
		if (s[0]=='E') break;
		n=strlen(s);
		for (i=n;i>=0;i--)
			s[i+i+2]=s[i],s[i+i+1]='#';
		s[0]='*';
		id=maxlen=0;
		for (i=1;i<=n+n+1;i++){
			if (p[id]+id>i) p[i]=min(p[id+id-i],p[id]+id-i);
				else p[i]=1;
			while (s[i-p[i]]==s[i+p[i]]) p[i]++;
			if (p[i]+i>p[id]+id) id=i;
			maxlen=max(maxlen,p[id]); 
		}
		printf("Case %d: %d\n",T,maxlen-1);
	}
	return 0;
}


[HDU 4513]

大概就是在裸的最长回文子串的基础上添加了限制条件,选出的数列要构成类似金字塔形状,先递增再递减。

一开始没注意要选连续的,被坑了好久。。。还以为以为是个什么神题。。。。

这个限制条件是很好处理的,首先我们要保证已经求出的回文数列都是满足要求的,那么对于当前i,如果是由之前的对应点直接对称得到的回文数列,那么肯定是满足要求;否则按照Manacher是依次向外扩张继续找,我们只要保证这时候找的数是依次递增的就行了。注意Manacher算法原本处理字符串时是添加“#”,我在处理数列时是以0来代替,这样在向两边寻找的时候要注意一下。

等于是在Manacher模板上加一句话就可以了。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;

const int N=2*1e5+10;
int n,a
,p
,maxlen,id;
int i,T;

int main(){
	scanf("%d",&T);
	while (T--){
		memset(p,0,sizeof p);
		scanf("%d",&n);
		for (i=0;i<n;i++){
			scanf("%d",&a[i+i+2]);
			a[i+i+1]=0;
		}
		a[0]=-1;a[n+n+2]=-2;
		id=maxlen=0;
		for (i=1;i<=n+n+1;i++){
			if (p[id]+id>i) p[i]=min(p[id+id-i],p[id]+id-i);
				else p[i]=1;
			while (a[i-p[i]]==a[i+p[i]] && (a[i-p[i]]==0 || a[i-p[i]]<=a[i-p[i]+2])) p[i]++;
											//这边要注意一下 
			if (p[id]+id<p[i]+i) id=i;
			maxlen=max(maxlen,p[i]);
		}
		printf("%d\n",maxlen-1);
	}
	return 0;
}


[HDU 3613]

大意是要你把一段字符串分成两段,每个字母对应一个价值,分出的串如果是个回文串,那么价值就是所有字母价值之和,如果不是回文串价值就为0,求最大可以得到的价值。

首先用Manacher预处理出每个点的最长回文子串,然后暴力枚举第一段的中点i,先判断是不是半径可以延伸到1,也就是能不能之前整个都保证是一段回文串,然后我们也可以知道右边能延伸到哪里,这样我们还可以求出第二段的中点在哪里,然后再根据中点判断一下剩下的部分是不是能构成回文串就可以了。好吧我语文不怎么好。。。有点说不清。。。反正脑补脑补那个意思就出来了。。。

但是要注意至少要分成两段,所以第一段的右边端点不能到n。

总觉得这个算法有点缺陷。。。反正都AC了管他呢。。。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;

const int N=2*1e6+10;
char s
;
int n,p
,id,a[27],sum
;
int i,j,T;
int ans,x;

int main(){
	scanf("%d\n",&T);
	while (T--){
		for (i=1;i<=26;i++) scanf("%d",&a[i]);getchar();
		scanf("%s",s);
		n=strlen(s);
		for (i=n;i>=0;i--) s[i+i+2]=s[i],s[i+i+1]='#';
		for (i=1;i<=n+n+1;i++) 
			if (s[i]!='#') sum[i]=sum[i-2]+a[s[i]-96];
				else sum[i]=sum[i-1];
		s[0]='*';
		id=0;
		for (i=1;i<=n+n+1;i++){
			if (p[id]+id>i) p[i]=min(p[id+id-i],p[id]+id-i);
				else p[i]=1;
			while (s[i-p[i]]==s[i+p[i]]) p[i]++;
			if (p[i]+i>p[id]+id) id=i;
		}
		
		ans=0;
		for (i=2;i<=n+n+1;i+=2){
			x=0;
			if (i-p[i]+1==1 && i+p[i]-1!=n+n+1) x+=sum[i+p[i]-1];
			int j=(i+p[i]+n+n+1)>>1;
			if (j+p[j]-1==n+n+1) x+=sum[n+n+1]-sum[j-p[j]];
			ans=max(ans,x);
		}
		printf("%d\n",ans);
	}
	return 0;
}


滔天巨浪奔涌咆哮,他的前方是远方!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: