Palindrome Partition

Palindrome Partition

题目

称一个字符串是好的,当且仅当它是一个长度为偶数的回文串或由若干长度为偶数的回文串拼接而成。

给定一个长度为 \(n\) 的字符串 \(s\),求有多少 \(s\) 的子串是好的。

\(1\le n\le5\times10^5\)\(s\) 仅包含小写字母。

思路

AI 优化

以下论证可能有些绕,建议结合示意图理解。

定义:本文中所有回文串均指长度为偶数的回文串

唯一分解性质

我们约定:一个好的字符串必须由极多的回文子串拼接而成。例如 abbaabba 会被拆分为 abba|abba(两个回文串),而非视为单个长回文串。通过数学归纳可证,这种分解方式是唯一的:

  1. 基础:空串显然成立。
  2. 归纳:假设某回文串是另一回文串的真前缀,则长串必可拆分为若干短回文串。例如设 \(A=BC\),其中 \(B\) 是回文前缀,\(C\) 也必为回文(利用回文对称性可证)。

预处理极短回文串

以每个位置 \(i\) 开头的极短回文串至多一个,总数不超过 \(n\)。预处理方法:

  1. Manacher预处理:求出以每个对称中心的最长回文半径 \(l_i\)。注意这里仅处理偶数长度回文(即对称中心在两个字符之间)。
  2. 区间覆盖:对每个位置 \(i\),找到能覆盖它的最长回文区间 \([L,R]\)。此时极短回文长度 \(f_i = R-i+1\)(若存在)。

动态规划计数

\(dp_i\) 表示以 \(i\) 开头的合法子串数。转移方程:

\[dp_i = \begin{cases} dp_{i+f_i} + 1, & \text{若存在极短回文串} \\ 0, & \text{否则} \end{cases} \]

解释:若存在长度为 \(f_i\) 的极短回文串,则所有合法子串可分为两类:

  • 单独取该极短回文串(贡献 \(1\)
  • 接续后续所有可能组合(贡献 \(dp_{i+f_i}\)

最终答案为 \(\sum_{i=1}^n dp_i\)

复杂度分析

Manacher 预处理 \(O(n)\),区间覆盖用差分数组实现 \(O(n)\),DP 转移 \(O(n)\),总时间复杂度 \(O(n)\)

以下回文串均指长度为偶数的回文串

以下讲得一坨,建议别看。

先简单刻画一下好的字符串。我们钦定一个好的字符串一定是由极多的回文串拼成的,也就是钦定 abbaabba 是由 \(2\)abba 拼成的,而不是由 \(1\)abbaabba 拼成的。

发现这样一个好的字符串只有唯一的刻画方式。只需要证明当一个回文串是另一个回文串的真前缀时,长的回文串一定可以拆成若干个回文串相拼接。不是很难证。

于是我们可以先预处理出极短的回文串。因为以 \(i\) 开头的极短回文串至多一个,所以总共不超过 \(n\) 个。然后 DP 计数。

\(f_i\) 表示以 \(i\) 开头的极短回文串长度,可以 manacher 求。manacher 可以求以 \(i\) 为对称中心的最长半径 \(l_i\)。那么我们知道 \([1,l_i]\) 这个区间都是回文的,随便区间覆盖一下即可。

\(dp_i\) 表示以 \(i\) 开头有多少子串合法。则 \(dp_i \gets dp_{i+f_i}+1\)。然后做完了。

复杂度 \(O(n)\)

code

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=5e5+7;
	int t,n;
	char s[N];
	int f[N];
	int dp[N];
	int p[N];
	ll ans;
	void init() {
		ans=0;
		memset(p+1,0,sizeof(int)*n);
		memset(f+1,0,sizeof(int)*n);
		memset(dp+1,0,sizeof(int)*n);
	}
	void manacher() {
		int r=0,mid=0;
		s[n+1]=s[0]+1;
		assert(s[n+1]<'a' || s[n+1]>'z');
		rep(i,1,n) {
			if(i<=r) p[i]=min(p[2*mid-i],r-i);
			while(s[i+p[i]+1]==s[i-p[i]]) p[i]++;
			if(i+p[i]>r) r=i+p[i], mid=i;
		}
	}
	int ne[N];
	int find(int u) {
		if(u==ne[u]) return u;
		return ne[u]=find(u+1);
	}
	void calc_f() {
		rep(i,1,n) ne[i]=i;
		rep(i,1,n) if(p[i]) {
			int l=i-p[i]+1,r=i;
			for(int u=find(l);u<=r;u=find(u+1)) ne[u]=u+1, f[u]=(i-u+1)<<1;
		}	
	}
	void calc_dp() {
		dp[n+1]=0;
		per(i,n-1,1) if(f[i]) {
			ans+=dp[i]=1+dp[i+f[i]];
		}
	}
    void main() {
		sf("%d",&t);
		while(t--) {
			sf("%d%s",&n,s+1);
			init();
			manacher();
			calc_f();
			calc_dp();
			pf("%lld\n",ans);
		}
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    wing_heart :: main();
}
posted @ 2025-02-03 17:51  wing_heart  阅读(24)  评论(0)    收藏  举报