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

本文提供了一种通过哈希优化的算法,用于解决最长双回文串问题。该方法通过枚举分割点,分别计算前缀和后缀的最长回文串长度,并利用哈希表在O(n)时间内验证,解决了问题的线性复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

补一个题解区没有的解法。

解法

通过哈希实现的线性做法,受讨论区启发。

考虑枚举双回文串的分割点,即分割点左右各是一个回文串,对于每个分割点,我们最大化两个回文串的长度,而且两个回文串是互不影响的。

问题转化为求原串的所有前缀的最长回文后缀和所有后缀的最长回文前缀。因为两个问题求法是类似的,所以这里只说如何求所有前缀的最长回文后缀。

我们记 p r e i pre_i prei 表示前 i i i 个字符组成的最长回文后缀。显然, p r e i ≤ i pre_i \le i preii。再思考一下,可以发现 p r e i ≤ p r e i − 1 + 2 pre_i \le pre_{i-1}+2 preiprei1+2,因为回文中心不可能向左移动,否则 p r e i − 1 pre_{i-1} prei1 将会变得更大,不符合定义。我们可以从 min ⁡ ( i , p r e i − 1 + 2 ) \min(i,pre_{i-1}+2) min(i,prei1+2) 开始向 1 1 1 暴力枚举 p r e i pre_i prei,使用哈希 O ( 1 ) \mathcal{O}(1) O(1) 检验。

乍一看,这个做法是暴力,不能保证复杂度。但发现每次 p r e pre pre 的上界只能增加 2 2 2,一共只能增加 2 n 2n 2n,所以只会暴力检验 O ( n ) \mathcal{O}(n) O(n) 次,所以总复杂度是线性。

代码

#include<bits/stdc++.h>
namespace fast_IO
{
	#define Getchar() p1==p2 and (p2=(p1=Inf)+fread(Inf,1,1<<21,stdin),p1==p2)?EOF:*p1++
	char Inf[1<<21],*p1,*p2;
	inline bool inrange(const char &ch)
	{
		if(ch>=33 && ch<=126) return true;
		return false;
	}
	inline void read(std::string &st,char c=Getchar())
	{
		st.clear();
		while(!inrange(c)) c=Getchar();
		while(inrange(c)) st+=c,c=Getchar();
	}
};
using namespace fast_IO;
#define int long long
std::string st;
int pre[100010],suf[100010],n,has1[100010],has2[100010],pw[100010],ans;
const int base=131,p=998244353;
inline int cal1(const int l,const int r)
{
	if(l>r) return 0;
	return (has1[r]-has1[l-1]*pw[r-l+1]%p+p)%p;
}
inline int cal2(const int l,const int r)
{
	if(l>r) return 0;
	return (has2[l]-has2[r+1]*pw[r-l+1]%p+p)%p;
}
inline void solve()
{
	pre[1]=1;
	for(int i=2,len;i<=n;i++)
	{
		len=std::min(i,pre[i-1]+2);
		while(cal1(i-len+1,i)!=cal2(i-len+1,i)) len--;
		pre[i]=len;
	}
	suf[n]=1;
	for(int i=n-1,len;i;i--)
	{
		len=std::min(n-i+1,suf[i+1]+2);
		while(cal1(i,i+len-1)!=cal2(i,i+len-1)) len--;
		suf[i]=len;
	}
}
signed main()
{
	read(st),n=st.size(),st="#"+st,pw[0]=1;
	for(int i=1;i<=100000;i++) pw[i]=pw[i-1]*base%p;
	for(int i=1;i<=n;i++) has1[i]=(has1[i-1]*base+st[i]-'a'+1)%p;
	for(int i=n;i;i--) has2[i]=(has2[i+1]*base+st[i]-'a'+1)%p;
	solve();
	for(int i=1;i<n;i++) ans=std::max(ans,pre[i]+suf[i+1]);
	std::cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值