题解:[国家集训队] 最长双回文串

题目传送门

如果你还不会Manacher:看这里

题意分析

首先,我们发现题目要求最长回文串,自然而然地想到 Manacher。

但是当我们求出最长回文半径 \(p_i\) 后,却发现不知道如何进一步做题了。

如果枚举两个回文串分别的回文中心,那么就是一个 \(\mathcal O\left(n^2\right)\) 的算法,考虑到 \(2\leq \vert S \vert \leq 10^5\),不可取。

我们考虑记录以 \(i\) 为右端点的最长回文串长度 \(r_i\) 和以 \(i\) 为左端点的最长回文串长度 \(l_i\),那么答案就是 \(\max(l_i+r_i)\)。(暂不考虑 Manacher 插入字符)

那么也就是 Manacher 中 \(p_i\) 暴力扩展完得到最终 \(p_i\) 后,更新 \(l_{i-p_i+1}\)\(r_{i+p_i-1}\)

最后 \(\mathcal O\left(n\right)\) 递推 \(l_i=\max\left(l_i,l_{i-2}-2\right)\)\(r_i=\max\left(r_i,r_{i+2}-2\right)\) 即可。

最长回文串更新

更新

在这一部分,“最长回文串”指的是那些无法继续扩展的回文串,而不是实际意义上的最长回文串。

例如对于 \(\texttt{abcbadddaddd}\)\(\texttt{abcba}\) 是“最长回文串”,因为其无法继续向两边扩展。

当然,原来意义上的最长回文串仍是此意义下的“最长回文串”,因为其无法继续扩展(\(\texttt{dddaddd}\))。

(以下不再使用引号以便于区分)

事实上,我们之前所得到的 \(l_{i-p_i+1}\)\(r_{i+p_i-1}\) 都是作为最长回文串的,明显还有很多信息没有处理到。

那么为什么直接递推 \(l_i=\max\left(l_i,l_{i-2}-2\right)\)\(r_i=\max\left(r_i,r_{i+2}-2\right)\) 就行呢?

需要明确的是,我们此时的 \(i\) 都是 Manacher 所插入的字符

先看看 \(l_{i-p_i+1}\)\(r_{i+p_i-1}\) 具体怎么赋值:

只需要 \(l_{i-p_i+1}=\max\left(l_{i-p_i+1},p_i-1\right)\) 即可。(\(r_{i+p_i-1}\) 同理)

因为,\(p_i\) 包括回文中心 \(i\),两边分别共计 \(\frac{p_i-1}2\) 个有效字符,总共就是 \(p_i-1\)(可以自己尝试手推)。

那么此时有值后面会提到!)的 \(l_i,r_i\) 就都是某个最长回文串的边界。

那么我们使用 \(l_{i-2}\) 获取到边界,再减去被我们去除的 \(2\) 个字符(回文,左右凉拌各一个)就得到了正确的 \(l_i\)。(\(r_i\) 同理)

错误

我当时调的时候,把 \(l_{i-p_i+1}\)\(r_{i+p_i-1}\) 的更新放在了 Manacher while() 扩展 \(p_i\) 的循环内部,导致一直 \(\text{WA}\) 在 #5。其实这就涉及到问题了,这会导致之后 \(l_i\)\(r_i\) 递推时不是最大,进而导致错误。

其实就是因为原本不应该有值的地方有了一个不正确的值。

AC代码

//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=1e6;
int top,p[2*N+1],l[2*N+1],r[2*N+1];
char s[2*N+1]={'~'};
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	char ch;
	while(cin>>ch){
		s[++top]='#';//top=1开始插入#,之后都遍历[1,top]
		s[++top]=ch;
	}s[++top]='#';
	for(int i=1,mid=0,R=0;i<=top;i++){
		if(i<R)p[i]=min(p[2*mid-i],R-i);
		while(s[i-p[i]]==s[i+p[i]])p[i]++;
		l[i-p[i]+1]=max(l[i-p[i]+1],p[i]-1);
		r[i+p[i]-1]=max(r[i+p[i]-1],p[i]-1);
		if(i+p[i]>R)R=i+p[i],mid=i;
	}//注意循环方向和数组越界:l必须从i=3开始,否则i-2会越界
	for(int i=top;i>=3;i-=2)r[i]=max(r[i],r[i+2]-2);
	for(int i=3;i<=top;i+=2)l[i]=max(l[i],l[i-2]-2);
	int ans=0;
	for(int i=1;i<=top;i+=2){
		if(l[i]>0&&r[i]>0)ans=max(ans,l[i]+r[i]);
	}printf("%d\n",ans);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2025-07-20 12:30  TH911  阅读(4)  评论(0)    收藏  举报