#回文自动机#CF932G Palindrome Partition
题目
给定一个串,把串分为偶数段,假设分为了 \(s_1,s_2,s_3,\dots,s_k\),求满足 \(s_1=s_k,s_2=s_{k-1},\dots\) 的方案数
分析
将原串变成 \(s_1s_ns_2s_{n-1}s_3s_{n-2}\dots\) 的形式,就转化为将字符串划分为若干个偶回文串的方案数。
维护回文树,暴力跳 fail 显然不可以,而 \(s\) 的所有回文后缀按照长度排序后,可以划分成 \(\log |s|\) 段等差数列。
回文树上的每个节点 \(u\) 需要多维护两个信息,\(diff[u]\) 和 \(slink[u]\)。\(diff[u]\) 表示节点 \(u\) 和 \(fail[u]\) 所代表的回文串的长度差,即 \(len[u]-len[fail[u]]\)。
\(slink[u]\) 表示 \(u\) 一直沿着 \(fail\) 向上跳到第一个节点 \(v\),使得 \(diff[v] \neq diff[u]\),也就是 \(u\) 所在等差数列中长度最小的那个节点。
\(g[v]\) 表示 \(v\) 所在等差数列的 \(dp\) 值之和,且 \(v\) 是这个等差数列中长度最长的节点,则 \(g[v]=\sum_{slink[x]=slink[v]} dp[i-len[x]]\),这里 \(i\) 是当前枚举到的下标。
因此,\(g[p]=g[fail[p]]+dp[i-len[slink[p]]-diff[p]]\),加上 \(fail[p]\) 当且仅当是同一个等差数列,这样就维护好了,\(p\) 跳 \(slink\) 链求出 \(dp[i]=\sum g[p]\)
时间复杂度 \(O(n\log n)\),\(\log\) 来自跳 \(slink\) 的时间。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1000011,mod=1000000007;
char s[N],str[N]; int n,dp[N],f[N];
struct PAM{
int trie[N][26],fail[N],len[N],dep[N],diff[N],slink[N],lst,cnt;
void BUILD(){fail[0]=cnt=1,len[1]=-1;}
int getf(int n,int now){for (;s[n-len[now]-1]^s[n];now=fail[now]); return now;}
void Insert(int n){
int Lst=getf(n,lst);
if (!trie[Lst][s[n]-97]){
int now=++cnt;
len[now]=len[Lst]+2;
fail[now]=trie[getf(n,fail[Lst])][s[n]-97];
trie[Lst][s[n]-97]=now;
diff[now]=len[now]-len[fail[now]];
if (diff[now]==diff[fail[now]]) slink[now]=slink[fail[now]];
else slink[now]=fail[now];
}
lst=trie[Lst][s[n]-97];
}
}pam;
void Mo(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
int main(){
pam.BUILD(),scanf("%s",str+1);
n=strlen(str+1),dp[0]=1;
for (int i=1;i<=n;++i)
if (i&1) s[i]=str[(i+1)>>1];
else s[i]=str[n-(i>>1)+1];
for (int i=1;i<=n;++i){
pam.Insert(i);
for (int p=pam.lst;p;p=pam.slink[p]){
f[p]=dp[i-pam.len[pam.slink[p]]-pam.diff[p]];
if (pam.slink[p]!=pam.fail[p]) Mo(f[p],f[pam.fail[p]]);
if (!(i&1)) Mo(dp[i],f[p]);
}
}
return !printf("%d",dp[n]);
}

浙公网安备 33010602011771号