//主要给自己看,写这个是为了学新的算法加深印象
ACM队这两天新出的字符串专题对我这种渣渣来说是比较难的,什么KMP,manacher,trie字典树,hash,ac自动机,这些我现在就勉强会个kmp算法,这个还是之前会长大大现场教学教的(我太菜了)...
刚看到这道题,想起以前做蓝桥杯的时候一道有关最小回文代价的题,题目翻译过来大概意思是就是给一个字符串,要求最少添加多少字符可以变为回文串,大概思路是对源串和逆串做LCS,源串长度再减去LCS的长度就是最小回文代价。
这道题刚开始我本来以为是源串和逆串求最长公共子串的,后来发现串长度太大了会超时,想了半天想不到什么降低复杂度的方法,网上一搜才知道原来有一种O(n)的算法叫manacher(马拉车算法)。
网上看了篇大佬的博客大致搞懂了,跟着大佬的思路写了遍代码....
大佬博客链接 点击打开链接
马拉车算法大概就是像KMP那样要求个Len数组,先在源字符串所有字符间以及首尾添加个#字符,为了避免越界在首尾加2个不同的其它字符比如@、*之类的。
然后遍历一遍,用right记录下之前找到的回文串最右端的边界(不包含),pos记录该回文串中点位置,每次i遍历时,判断i有没有超出right,没有的话Len[i]=min(Len(2*pos-i),right-i) (前者是i关于pos对称位置),超出的话Len[i]=1; 不过这个求出的Len[i]不一定是最终的,所以还需要一个while循环来更新
while(st[i-Len[i]] == st[i+Len[i]]) Len[i]++; //st为变换后的字符串
然后更新下pos,right; 记录下最大的Len[i]为ans,ans-1就是要求的结果
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
char ch1[120000]; //源串
char ch2[300000]; //变换后的串
int Len[300000];
int len,newLen; //源串和新串长度
void Init(){
len = strlen(ch1);
ch2[0] = '@';
int pos = 0;
for(int i = 1;i <= 2*len;i++){
if(i&1) //i是奇数
ch2[i] = '#';
else
ch2[i] = ch1[pos++];
}
ch2[2*len+1] = '#';
ch2[2*len+2] = '*';
ch2[2*len+3] = 0;
newLen = 2 * len + 1;
}
void Manacher(){
int pos,right,ans;
pos = ans = Len[1] = 1;
right = 2;
for(int i = 2;i <= newLen;i++){
int j = 2*pos-i; //i关于pos的对称位置
if(i < right)
Len[i] = min(Len[j],right-i);
else
Len[i] = 1;
//注意else后没有{}
while(ch2[i-Len[i]] == ch2[i+Len[i]])
Len[i]++;
if(Len[i] + i > right) {
pos = i;
right = i+Len[i];
}
ans = max(ans,Len[i]);
}
printf("%d\n",ans-1);
}
int main(){
while(scanf("%s",ch1) != EOF) {
Init();
Manacher();
}
return 0;
}