马拉车

引入

给你一个长度为 \(n\) 的字符串,求最长回文子串的长度。(\(n \le 10^5\)

思路

容易想到暴力枚举每个点作为中点,往两边扩展,最后取个最大值。发现暴力极限的时间复杂度为 \(O(n^2)\)

回看暴力的过程,发现如果每个点都要向两边扩展的话,会重复判断许多点,例如:
image

于是我们可以发现,对于已经判断成回文串的序列,它的左右两边是完全对称的,只需要考虑左边,右边的直接 copy 过来就好了。

我们记录两个变量和一个数组,\(mx\) 表示回文串扩展到最右边的下标,\(id\) 表示最右边的回文串的中心,\(a_i\) 表示以 \(i\) 为中心点最大扩展的长度。

为了便于考虑奇偶性,我们在处理前在每个字符与下一个字符之前加一个#,这样保证序列长度始终为奇数。

s="@#";
for(int i=1;i<=n;i++){
	char c;
	cin>>c;
	s.push_back(c);
	s.push_back('#');
}

接下来开始分类讨论(\(i\) 关于 \(id\) 的对称点为 \(2 \times id-x=j\)):

情况1:\(i < mx\)\(a[j] <= mx-i\)

image

因为左右两边完全对称,所以 \(a_i = a_j\)

情况2:\(i < mx\)\(a[j] > mx-i\)

image

因为 \(a_j\) 有一段不在回文串里,不能对称过去,于是 \(a_i\) 只能保留 \([i,mx]\)\([2 \times id-mx ,j]\)) 那一段,即 \(a[i] = mx-i\)

情况3:\(i > mx\)

那就 copy 不了了,初始化 \(a_i = 1\),暴力老实算。

for(int i=1; i<s.size(); i++) {
	if(i<mx) a[i]=min(a[2*id-i],mx-i);
	else a[i]=1;

处理好 \(a_i\) 后就直接暴力扩展。

while(s[i-a[i]]==s[i+a[i]]) {
	a[i]++;
}

扩展完后更新mx。

if(i+a[i]>mx) {
	mx=i+a[i]-1;
	id=i;
}

然后答案就是对于每个 \(i\)\(a_i -1\) 的最大值就好了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int N=1e6+5;
int n,a[N],id,mx,ans;
string s;
signed main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	s="@#";
	for(int i=1;i<=n;i++){
		char c;
		cin>>c;
		s.push_back(c);
		s.push_back('#');
	}
	for(int i=1;i<s.size();i++){
		if(i<mx) a[i]=min(a[2*id-i],mx-i);
		else a[i]=1;
		while(s[i-a[i]]==s[i+a[i]]){
			a[i]++;
		}
		if(i+a[i]>mx){
			mx=i+a[i];
			id=i;
		}
		ans=max(ans,a[i]-1);
	}
	cout<<ans<<"\n";
	return 0;
}

posted @ 2026-03-28 09:16  Azarole  阅读(3)  评论(0)    收藏  举报