[Manacher]最长回文子串

很久没有写博客了
啪啪啪
写一些东西吧

最长回文子串怎么求呢
首先我们得知道什么是子串,给你一个长长的串,里面任意连续的一段就是它的子串,当然一个字符也是子串
接着什么是回文串呢 不好描述 但是看例子很容易懂:aba 121 1221 1
然后我们有一种很显然的寻找方法 当然是枚举中点 然后尽可能的往外扩大回文串的长度 这种算法花费的时间是N(字符串长度)*Mk(可扩展长度)Mk<N

当然这样的时间并不是很让人满意
于是Manacher这个人发明了一种算法
他是这样想的
假如我先前已经找到的回文串已经是很大了,比如已经覆盖了整个字符串那么长
那我找到它以后 就不要找了啊 根据回文串左右对称的特性
我们可以利用之前找到的可扩展的右端,以及扩展起始点
如果当前查找的被扩展最右端覆盖,那么当前查找的起始点可扩展的最小值就是以扩展起始点为对称中心相对称的那个点的扩展值
如果扩展到了边界,就可以继续扩展下去直到不能扩展,啪啪啪就结束了

非常精彩 但是有个问题 中点可能是一个字符 也可以是两个字符
分情况讨论?费劲
有一种很机智的方法 往字符串每个字符间掺同一个不属于这个字符串的字符然后前后各掺一个
显然假如字符串有n个 新字符串必为2*n+1 也就是只需要讨论一种情况了
至于到底要求长度还是求字符 就在求的时候开个最大值记录的存一下就行了

这个算法的时间花费是4*N(字符串长度)(建一遍,找一遍,长度因为扩大了一倍所以是2N)

下面是个打得很烂的模板

//Manacher算法
string s;
char inser='&';
char str[301000];
int len[301000];
void cvt(int n){
	str[0]=inser;
	for(int i=1;i<=(n<<1);i+=2){
		str[i]=s[i>>1];
		str[i+1]=inser;//cout<<str[i]<<" "<<str[i+1]<<" ";
	}
}
void getLen(int l){
	int p0=0,p=0;len[0]=1;
	int j;
	for(int i=1;i<=l;i++){
		if(i<=p){
			j=(p0<<1)-i;
			if(len[j]<(p-i))len[i]=len[j];
			else len[i]=p-i;
//			len[i]=min(len[2*p0-i],p-i);
		}
		else
			len[i]=1;
		int cl=i-len[i],cr=i+len[i],temp=len[i];
		while(cl>=0&&cr<=l&&str[cl]==str[cr]){
		//	if(str[cr]!=inser&&str[cr]>str[cr-2])break;
			temp++;
			cl--;cr++;
		}
		len[i]=temp;
		if((i+len[i]-1)>p){
			p=i+len[i]-1;
			p0=i;
		}
	}
}
int main(){
	while(cin>>s){
		int n;
		n=s.size();
		cvt(n);
		getLen(n<<1);
		int ans=0;
		for(int i=0;i<=(n<<1);i++)
		ans=max(ans,len[i]);
		cout<<ans-1<<endl;
	}
	return 0;
}

下面是一个稍微好一点的模板

//Proudly using c++11 by gwt
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int,int> pii;
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define fi first
#define se second
#define all(x) (x).begin(),(x).end()
//#define endl '
'
#define count2(x) __builtin_popcount(x)
#define countleadingzero(x) __builtin_clz(x)
#define debug(x) cerr << #x << " = " << x << endl;
inline ll read(){//LLONG_MIN LMAX=9,223,372,036,854,775,807
    ll s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0' && ch<='9')s=s*10+ch-'0',ch=getchar();
    return s*w;
}
namespace Manacher{
	const int maxn=3e7;
	string s;
	char inser='&';//用来填的字符 与s中字符不能相同 
	char str[maxn];//填充后的串 [0,2*n] 
	int len[maxn];//len[i]表示以i为中心的最长回文子串长度 
	void cvt(const int n){
		str[0]=inser;
		for(int i=1;i<=n*2;i+=2){
			str[i]=s[i>>1];
			str[i+1]=inser;//cout<<str[i]<<" "<<str[i+1]<<" ";
		}
	}
	void getLen(const int length){
		int p0=0,p=0;len[0]=1;
		int j;
		for(int i=1;i<=length;i++){
			
			if(i<=p){
				j=p0-(i-p0);//以 p0表示最长的回文子串的中心对i作对称后的j点 
				//这里 如果比扩展的边界小 那么只能取到len[j] 
				if(len[j]<(p-i))len[i]=len[j];
				else len[i]=p-i;//如果比扩展的边界大 那么只能取到扩展的边界 
	//			len[i]=min(len[2*p0-i],p-i);
			}
			else
				len[i]=1;
			//cl表示扩展的左起始点 cr表示扩展的右起始点 temp表示当前的回文串长度 
			int cl=i-len[i],cr=i+len[i],temp=len[i];
			while(cl>=0&&cr<=length&&str[cl]==str[cr]){
			//	if(str[cr]!=inser&&str[cr]>str[cr-2])break;
				temp++;
				cl--;cr++;
			}
			len[i]=temp;
			if((i+len[i]-1)>p){//p表示最长的回文子串的右端 
				p=i+len[i]-1;
				p0=i;//p0表示最长的回文子串的中心 
			}
		}
	}	
} ;

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>Manacher::s;
	const int n=Manacher::s.size();
	Manacher::cvt(n);//将长度为n的串s转化成长度为2*n+1的串 
	const int n2=2*n;
	Manacher::getLen(n2);//得到 长度为2*n+1的串的最长回文子串的len[i]
	int ans=0;
	for(int i=0;i<=n2;i++)
	ans=max(ans,Manacher::len[i]);
	cout<<ans-1<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/passguan/p/9501192.html