【小结】manacher

现在要解决下列问题:

给定字符串 \(s\),求 \(s\) 的最长回文子串的长度。

首先,暴力是 \(\mathcal O(n^3)\) 的。

然后暴力加上哈希是 \(\mathcal O(n^2)\) 的。

然后奇偶性分类并二分哈希是 \(\mathcal O(nlogn)\) 的。

那有没有线性做法呢?

有的有的,就是 manacher。

首先考虑一个更有思维性的 \(\mathcal O(n^2)\) 暴力。

选取中间点,往两边扩展,奇偶分类一下。

首先对于奇偶性分类太麻烦了,有没有小技巧?

有的有的,在每一个字符两边插入 \(#\)(开头结尾也要插入)。

然后以每一个位置为中间位置,往两边找回文串。

然后有一个定义:最长回文半径,表示往左/往右延伸的最大长度。设 \(i\) 作为中间的最大回文半径为 \(p_i\)。那么整个串的长度为 \(2p_i-1\)\(#\) 比有效字符多一个,所以 \(\frac{(2p_i-1-1)}{2}=\frac{2p_i-2}{2}=p_i-1\) 就是对应原串的长度。

现在的目标转化为了求最长回文半径。

设前面的所有 \(i\) 之中,最大的结尾为 \(r\),对应的 \(i\)\(mid\)

此时我们用这对 \(mid,r\) 来计算 \(i\) 的答案。

首先 \(i>mid\),因为 \(mid\) 是在 \(i\) 前面枚举到的。

接下来,分两种情况:

  1. \(i>r\),直接 \(p_i=1\),从 \(1\) 开始枚举,因为没有任何可以继承的东西了。

  2. \(i\leq r\)。此时 \(i\) 关于 \(mid\) 的对称点为 \(2\times mid-i\),如果 \(p_{2\times mid-i}< r-i+1\),那么关于 \(mid\) 对称过来就变成了 \(p_i\)。否则,直接继承到 \(r-i+1\),因为后面的不一定对称。接下来枚举。

其实就是确定 \(p_i\) 的下界,往上枚举。

注意更新 \(mid,r\)

复杂度分析:

对于 \(i>r\),每一次跳都会使得 \(r\) 增加。

对于 \(i\leq r\),如果 \(p_{2\times mid-i}< r-i+1\),那么下一个直接停止(因为肯定不一样),否则每一次都会拓展。也会让 \(r\) 增加。

总之,就是每一次枚举,都会让 \(r\) 增加。

\(r\) 最多增加 \(n\) 次,所以时间复杂度:\(\mathcal O(n)\)

P3805 【模板】manacher

模板题,一模一样。

#include<bits/stdc++.h>
using namespace std;
/*

*/
struct FSI{
	template<typename T>
	FSI& operator >> (T &res){
		res=0;T f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
		res*=f;
		return *this;
	}
}scan;
const int N=1.1e7+10,M=2*N;
int n,i,m;
char s[N],t[M];
int p[M];
void manacher()
{
	int i,mid=0,r=0,ans=0;
	t[++m]='#';
	for (i=1;i<=n;i++)
	{
		t[++m]=s[i];
		t[++m]='#';
	}
	for (i=1;i<=m;i++)
	{
		if (i<=r) p[i]=min(p[2*mid-i],r-i+1);
		else p[i]=1;
		while (i-p[i]>=1&&i+p[i]<=m&&t[i-p[i]]==t[i+p[i]]) p[i]++;
		ans=max(ans,p[i]-1);
		if (i+p[i]-1>r) r=i+p[i]-1,mid=i;
	}
	printf("%d\n",ans);
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	manacher();
	return 0;
}

P11749 「TPOI-1C」Standard Problem.

下文令 \(A\&B\) 表示 \(A\)\(B\) 拼接之后,开头在 \(A\) 结尾在 \(B\) 的方案数。

首先,有递推公式:

\(S^1=1\)

\(S^2=S^1+2+1\&2\)

\(S^3=S^2+3+2\&3+1\&2\&3\)

\(\dots\)

差分,得到:

\(S^2-S^1=2+1\&2\)

\(S^3-S^2=3+2\&3+1\&2\&3\)

\(\dots\)

\(S^k-S^{k-1}=k+(k-1)\&k+\dots+1\&2\&\dots\&k\)

显然,这些东西的和加上 \(S^1\) 就是答案。

首先考虑 \(1,2,3,\dots,k\) 都长一样,所以长度相同的 \(1,2,3,\dots,k\)\(1\&2,2\&3,\dots,(k-1)\&k\) 诸如此类都一样。

再考虑一个惊人的结论:

\(1\&2\&3=1\&2\&3\&4=1\&2\&3\&4\&5=\dots\)

这是为啥呢?因为 \(s\)\(ss\)\(sss\) 回文等价。

所以对于两边的每一对位置,中间都是等价的。

处理出 \(S1,S2,S3\)

最后答案:

\(S1+(S2-S1)\times(k-1)+\frac{(k-1)\times(k-2)}{2}\times (S3-2\times S2+S1)\)

最后那个东西咋来的?我们要求 \(1\&2\&3\)

也就是 \(S^3-S^2-(S^2-S^1)=S^3-2\times S^2+S^1\)

时间复杂度:\(\mathcal O(n)\)。使用马拉车。

#include<bits/stdc++.h>
using namespace std;
/*

*/
struct FSI{
	template<typename T>
	FSI& operator >> (T &res){
		res=0;T f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
		res*=f;
		return *this;
	}
}scan;
typedef long long ll;
const int N=9e6+10,M=2*N,mod=998244353;
int T,n,i;
string s;
char t[M];
int p[M];
ll S1,S2,S3,ans,k;
ll manacher(string s)
{
	int i,n=s.size(),mid=0,r=0,m=0;
	ll ans=0;
	t[++m]='#';
	for (i=0;i<n;i++)
	{
		t[++m]=s[i];
		t[++m]='#';
	}
	for (i=1;i<=m;i++)
	{
		if (i<=r) p[i]=min(p[2*mid-i],r-i+1);
		else p[i]=1;
		while (i-p[i]>=1&&i+p[i]<=m&&t[i-p[i]]==t[i+p[i]]) p[i]++;
		ans=(ans+p[i]/2)%mod;
		if (i+p[i]-1>r) r=i+p[i]-1,mid=i;
	}
	return ans;
}
void work()
{
	cin>>n>>k>>s;
	S1=manacher(s);
	S2=manacher(s+s);
	S3=manacher(s+s+s);
	ans=(S1+(k-1)*((S2-S1+mod)%mod)%mod+(k-1)*(k-2)/2%mod*((S3-2*S2+S1+mod+mod)%mod)%mod)%mod;
	cout<<ans<<"\n";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>T;
	while (T--) work();
	return 0;
}
posted @ 2026-08-21 16:45  GUO120822  阅读(2)  评论(0)    收藏  举报