在字符串问题中,有一类经典的问题是求字符串中的最长回文子串,而解决这类问题的算法也很多,例如后缀树或者分治+拓展KMP。但是后缀树的极为复杂,没有实用性;分治+拓展KMP的复杂度达到了O(NlogN),并不能算是非常高效。对此,我们可以学习一种专门的算法:Manacher算法。
Manacher算法的核心可以用一句话来概括:在回文子串中找回文子串,带上这个思想应该会更好理解。
可以参看星夜永恒的blog,写的非常好,一些图也画的十分贴切。
[POJ 3974]
模板题,没什么好说的。
#include
#include
#include
using namespace std;
const int N=2*1e6+10;
char s[N];
int n,p[N],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
#include
#include
using namespace std;
const int N=2*1e5+10;
int n,a[N],p[N],maxlen,id;
int i,T;
int main(){
scanf("%d",&T);
while (T--){
memset(p,0,sizeof p);
scanf("%d",&n);
for (i=0;ii) 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
大意是要你把一段字符串分成两段,每个字母对应一个价值,分出的串如果是个回文串,那么价值就是所有字母价值之和,如果不是回文串价值就为0,求最大可以得到的价值。
首先用Manacher预处理出每个点的最长回文子串,然后暴力枚举第一段的中点i,先判断是不是半径可以延伸到1,也就是能不能之前整个都保证是一段回文串,然后我们也可以知道右边能延伸到哪里,这样我们还可以求出第二段的中点在哪里,然后再根据中点判断一下剩下的部分是不是能构成回文串就可以了。好吧我语文不怎么好。。。有点说不清。。。反正脑补脑补那个意思就出来了。。。
但是要注意至少要分成两段,所以第一段的右边端点不能到n。
总觉得这个算法有点缺陷。。。反正都AC了管他呢。。。
#include
#include
#include
using namespace std;
const int N=2*1e6+10;
char s[N];
int n,p[N],id,a[27],sum[N];
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;
}