manacher马拉车算法(学习笔记)
例题:给出一个只由小写英文字符a,b...y,z组成的字符串S,求S中最长回文串的长度.字符串长度为n;
由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里),这样以后回文串的长度必定是奇数。例如:
bccb ----> ~|b|c|c|b|
定义一个数组int f[],其中f[i]表示以 i 为中心的最长回文的半径;例如字符串bcacb(这个地方不能理解的,自己在纸上举几个例子就明白了):
f[1]=1, f[2]=1, f[3]=3, f[4]=1, f[5]=1;
设置两个变量maxr 和 mid 。maxr 代表以 mid 为中心的最长回文的右边界,也就是maxr = mid + f[mid]。
注意:maxr不是我们读入的字符串的右边界,而是我们当前已知的回文串的右边界,我们就是要不断使它往右扩展,即回文串长度不断增大
假设我们现在求f[i],也就是求以 i 为中心的最长回文半径,如果i < maxr,那么:
if(i<maxr){
f[i]=min(f[(mid<<1)-i],maxr-i);
}
2 * mid - i是 i 关于 mid 的对称点(可根据中点公式证明),设i,j关于mid对称,则(i+j)/2=mid,j=2*mid-i;
f[j]表示以 j 为中心的最长回文半径,因此我们可以利用f[j]来加快查找。
#include<bits/stdc++.h>
using namespace std;
int cnt,maxr,mid,f[25000005];
//cnt表示读入字符个数,maxr,mid,f数组意义如上文所述,
int maxans;
//记录答案,即最长回文串的长度
char ch,s[25000005];
//ch循环读入字符所用,s数组储存读入的字符串
int main(){
s[++cnt]='~';
//使字符串第一个字符为'~',具体作用请见下面代码
s[++cnt]='|';
while(~scanf("%c",&ch)){
if(ch>='a'&&ch<='z'){
s[++cnt]=ch;
s[++cnt]='|';
//读入字符串的同时,读入分隔符
}
else break;
}
for(int i=1;i<=cnt;i++){
//枚举读入的每个字符
if(i<maxr)
f[i]=min(f[(mid<<1)-i],maxr-i);
//maxr表示以 mid 为中心的最长回文串的右边界;
//(mid<<1)-i表示i关于mid的对称点;
//maxr-i表示半径可达到的最大值(i+f[i]<=maxr);
else f[i]=1;
//否则该字符本身构成一个独立的回文串,半径为1
while(s[i-f[i]]==s[i+f[i]])
f[i]++;
//以i为中心,i-f[i],i+f[i]分别是回文串左右两边端点
//如果相等,说明以i为中心的回文串的半径还可以继续增大
//此处体现了我们之前使字符串0号下标为字符'~'的作用
//防止数组越界,使循环暂停;
if(f[i]+i>maxr) maxr=f[i]+i,mid=i;
//如果可以更新右边界maxr,则更新右边界;
//同时也要更新mid(mid随maxr变化);
if(f[i]>maxans) maxans=f[i];
//更新最大答案
}
printf("%d",maxans-1);
//输出ans-1,为什么要减1,自己手动举几个例子就明白了
return 0;
}
练习:对于一个长度为偶数的序列\(a_1,a_2,a_3...a_n\),定义这个序列为好的序列,当且仅当\(a_1+a_n=a_2+a_{n-1}=a_3+a_{n-2}...\)。再定义一个对序列的翻滚操作,使所有元素向前移一个位置,第一个元素移到最后的位置。现在给你一个长度为偶数的序列\(b_1,b_2,b_3...b_n\),请问至少需要翻滚多少次才能使这个序列成为好的序列?
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define db double
using namespace std;
int l[2000005],a[4000005];
int n,i,mid,r,t;
ll tot;
inline int read(){
char ch;
while((ch=getchar())<'0'||ch>'9');
int res=ch^48;
while((ch=getchar())>='0'&&ch<='9')
res=(res<<1)+(res<<3)+(ch^48);
return res;
}
int main(){
n=read();
for(i=1;i<=n;++i){
a[i+n]=a[i]=read();
tot+=a[i];
}
//数组倍长,计算总和tot
t=tot/(n/2);
//计算每对匹配元素的和
i=0;
a[0]=-100000000;
//a[0]赋极大值,作用于下面循环停止
while(i<=n){//马拉车模板
if(i<r)
l[i]=min(l[(mid<<1)-i],r-i);
while(a[i+l[i]]+a[i-l[i]+1]==t)
l[i]++;
if(l[i])
l[i]--;
if(l[i]*2>=n){
printf("%d\n",i-(n/2));
return 0;
}
if(i+l[i]>r){
r=i+l[i];
mid=i;
}
i++;
}
printf("IMPOSSIBLE\n");
return 0;
}