suxxsfe

一言(ヒトコト)

manacher 笔记

P3805【模板】manacher算法:https://www.luogu.com.cn/problem/P3805

\(S\) 中的最大回文串长度,\(|S|\le 1.1\cdot 10^7\)

考虑一个 \(n^2\) 暴力,枚举对称轴(可能是以一个字符为对称轴,也可能是以两个字符的间隙),向外一位一位扩展,并更新答案
实测64分

但这个暴力也对 manacher 正解有了启示,先不考虑以字符间隙为对称轴的,考虑能不能 \(O(n)\) 求出来以某个字符为对称轴的回文子串最大长度

定义回文半径 \(len_i\),为以第 \(i\) 个字符为对称轴,能向外扩展的最大长度(包括它本身),也就是对应的回文串长度为 \(2len_i-1\)
如果现在以知一个点 \(pos,max=len_{pos}\),那么根据回文串的性质,区间 \([pos-max+1,pos-max-1]\) 是左右对称的
也就是如果某个点 \(x\in [pos-max+1,pos-max-1]\),它的回文半径为 \(len_x\),那么和 \(x\) 关于 \(pos\) 对称的点,也就是 \(2pos-x\),它的回文半径也至少\(len_x\)
当然前提是,这个以 \(x\) 为对称轴的回文串,没有超出区间 \([pos-max+1,pos-max-1]\)

就像上面这个图,红色区域由于关于 \(pos\) 对称,所以也一定会有右边这个以 \(2pos-x\) 为对称轴的回文串
而蓝色区域超出了这个区间,超出的部分(两边的绿色),必然不关于 \(pos\) 对称,不然这个区间就会继续往外延伸了,所以也就出现了错误
所以,实际上,这个回文半径至少为 \(\min(len_x,pos+max-(2pos-x))\),后面那项,实际上是 \((pos+max-1)-(2pos-x)+1\),也就是 \(2pox-x\)\(pos+max-1\) 的长度(就是说超过了这个区间的部分不能计算在其中)

但这里说的是至少为,什么情况下会比这个多?
\(len_x\ge pos+max-(2pos-x)\),也就是,这个回文半径已经从 \(2pos-x\) 一直延伸到了 \(pos+max-1\),剩下的因为出了区间而被截掉了(或者没有剩下的)
此时,它的回文半径,是可以继续向这个区间外扩展的(就是右边在扩展前已经顶到了区间右端点,然后一位一位右端加一,左端减一,这样更新 \(len\)
那么此时就暴力一位位比对来扩展,直到无法继续
这样就能利用之前的某个 \(len_x\) 来推知现在这个点的 \(len\)

那如何保证复杂度 \(O(n)\)
这里维护的 \(pos,max\),是使得 \(pos+max-1\) 最大的一组,每次也是用它来更新每个位置的 \(len\)
这样,每次暴力比较,如果成功,都会把 \(pos+max-1\) 向外扩展一位(此时的 \(pos\) 就变成了当前正在被更新回文半径的这一位,可以结合上面的图理解)
然后 \(pos+max-1\) 最多扩展到 \(n\),也就是扩展 \(n\) 次,所以复杂度控制在了 \(O(n)\)

但这样只是计算了以某一个字符为对称轴的情况
其实另一种情况想转化过来也不难,就是在每两个字符的中间都插入一个无意义的字符
这样,在原来是以两字符间隙为对称轴的情况,现在就是以这个插入的字符为对称轴
比如 aabaac,就变成了 #a#a#b#a#a#c#,这个 # 就是那个无意义字符
而对于一个 \(len_i\),它对应的回文串长度为 \(2len_i-1\),而这个回文串肯定是以 # 开头结尾(因为是最长的),所以实际长度是 \(\lfloor\dfrac{2len_i-1}{2}\rfloor=len_i-1\)
这也是为什么要在开头结尾也插入一个 #,这样让所以可能的回文串长度上都符合了这个式子

实际代码中,要在开头结尾的 # 前后,再插入两个不同的字符,为的是防止比对时越界
而且刚才的讲解是直到 \(x\),确定要更新的 \(2pos-x\),但实际代码中是要更新 \(i\),讲解里的 \(x\) 在代码里是 \(2pos-i\)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
char in[11000005],s[22000005];
int len[22000005];
int main(){
	scanf("%s",in+1);
	int n=std::strlen(in+1);
	for(reg int i=1;i<=n;i++) s[i+i]=in[i],s[i+i+1]=2;
	n+=n+1;
	int pos=0,max=0;
	s[0]=0;s[1]=2;s[n+1]=1;
	int ans=0;
	for(reg int i=1;i<=n;i++){
		if(pos+max-1<i) len[i]=1;
		else len[i]=std::min(len[pos+pos-i],pos+max-i);
		while(s[i+len[i]]==s[i-len[i]]) len[i]++;
		if(i+len[i]-1>=pos+max-1) pos=i,max=len[i];
		ans=std::max(ans,len[i]-1);
	}
	printf("%d",ans);
//		EN;for(reg int i=1;i<=n;i++) printf("%c %d\n",s[i],len[i]);
	return 0;
}
posted @ 2020-07-13 23:23  suxxsfe  阅读(121)  评论(0编辑  收藏  举报