首页 > 技术文章 > 【SHOI2011】双倍回文

Cry-For-theMoon 2021-03-07 17:25 原文

传送门

线性PAM的神仙们tql,蒟蒻只会manacher

考虑双倍回文串本身一定也是一个回文串,我们在manacher计算的过程中,一共只会产生 \(O(n)\) 个本质不同的回文串。

实际上,我们每次只需要计算新出现的那些回文串。归纳地去证明,如果一个子串不是新出现的,它关于 \(mid\)(就是manacher中,那个在计算 \(i\) 之前 \(p[j]+j\) 最远的那个 \(j\))一定有一个对称的串,在那个对称串,相似的,要么继续对称往前,总会遇到一次,是新出现的,此时这一连串个对称的相同的串都被计算了。所以我们每次只计算新产生的那些回文串。

首先,注意到双倍回文串长度是 \(4\) 的倍数,所以我们只能在当前字符为特殊字符(本文用 '#' 字符)时才更新答案。

显然,我们可以 \(O(1)\) 判断 \(S[x,y]\)(以 \(i\) 为中心)是否回文,\(O(1)\) 计算 \(S[x,y]\) 的真实字符个数。(这个不用讲)

判断 \(S[x,y](mid:i)\) 是否合法,首先计算出字符个数判断其是否为 \(4\) 的倍数,然后,注意到 \(S[x,y]\) 本身回文,只需要证明左半部分或者右半部分回文即可。因为我们还没算出右半部分的答案,所以我们判断左半部分是否回文。此时我们需要快速找到左半部分中间点,然后判断这个中心点 \(pos\)\(p[pos]\) 是否大于等于左半部分长度即可。(细节较多注意不要写挂)

//SHOI,2011
#include<bits/stdc++.h>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define per(i,a,b) for(ll i=(a);i>=(b);i--)
#define next Cry_For_theMoon
#define il inline
typedef long long ll;
using namespace std;
const int MAXN=1e6+10;
int n,ans,p[MAXN],pos,r;
char str[MAXN],tmp;
int main(){
	scanf("%d",&n);
	rep(i,1,n){
		scanf(" %c",&tmp);
		str[i*2]=tmp;
		str[i*2+1]='#';
	}
	str[1]='#';
	n=n*2+1;
	rep(i,1,n){
		if(i<=r)p[i]=min(r-i,(ll)p[pos*2-i]);
		while(i-p[i]>=1&&i+p[i]<=n&&str[i-p[i]]==str[i+p[i]])p[i]++;
		p[i]--;
		if(i+p[i]>r){
			if(str[i]=='#'){
				rep(j,max(i+4,(ll)r),i+p[i]){
					int len=j-i;
					if((len)%4)continue;
					if(p[i-len/2]>=len/2)ans=max(ans,len);
				}
			}
			pos=i,r=i+p[i];
		}
	}
	printf("%d",ans);
	return 0; 
}

推荐阅读