CF932G Palindrome Partition
\(\tt UPD:2020/12/27\) ,今天拿到这个以前做过的题,感觉有很多新的想法,所以来重构一下博客。
一、题目
二、解法
真的神题,我是看着yyb大佬的博客做的,换我自己肯定想不出来。
题目中分成\(k\)段肯定是直接做不了的,我们尝试对原问题做等价变化。
怎么个变化法?你会感受到这个相等的关系隔得太远了,我们想要近一点的关系,所以我们想要把后面的移到前面来,但是这时候相等关系又不行的,我们干脆把它转化成回文关系。(虽然想到下面的构造还是很难)
可以构造出一个新的字符串:\(S'=s[1]s[n]s[2]s[n-1]....\),问题就转化成了把\(S'\)划分成若干个长度为偶数的回文串。
考虑 \(dp\) ,设 \(f[i]\) 为划分到\(i\)的方案数,枚举一个长度为偶数的回文后缀,暴力转移的话时间复杂度是 \(O(n^2)\) , \(T\) 了。
考虑回文自动机,暴力转移的本质是从 \(last\) 处跳 \(fail\) ,有一个结论,如果回文后缀的长度 \(>len/2\) ,那么这些回文后缀就是等差的,根据这一点我们可以把 \(fail\) 链划分成若干个部分,是 \(\log\) 级别的。
其实这个 \(\log\) 的结论并不是空穴来风,以前我做过一道题:论战捆竹竿,那道题的中心结论是 \(\tt border\) 的等差数列是 \(\log\) 级别的,\(\tt border\) 和最长回文后缀是有其类似之处的,所以这两个结论是相同的。那道题我用的纯数学的方法证明,但是回文这个东西呢,画图会更好说明一些:
\(S\) 表示大回文串和第一个回文后缀空出来的这一部分,\(S^R\) 则表示其的翻转。那么用 \(x_1,x_2\) 这两个对称轴我们可以一直把这个等差的回文后缀推下去,直到推到 \(len/2\) 这个地方,所以划分成等差数列的数量就是 \(O(\log n)\)
哈哈,\(\tt WC\) 的题竟然和 \(\tt CF\) 的题撞了创意,这个等差数列的结论真是太有意思啦!
考虑一个部分的贡献,我们处理出到这一个部分顶端再 \(fail\) 的位置,可以看图:
设 \(f[i]\) 为 \(i\) 到这一部分顶端的贡献,考虑 \(f[i]\) 和 \(f[fail[i]]\) 的差异,实际上就是少了 \(dp[id-len[top[i]]-dif[i]]\) ,其中 \(top\) 是顶端再 \(fail\) 的位置,\(id\)是现在的字符串到的位置, \(dif\) 就是和 \(fail\) 的长度差异,\(f[fail]\) 一定会在前面更新(因为 \(\tt fail\) 其实是最长回文后缀,对称过去会有一个前缀会和这个后缀相等,那么就在之前访问过了),由于 \(dp\) 时位置的关系,我们只需要补齐顶端的位置的 \(dp\) 即可。
时间复杂度 \(O(n\log n)\) 。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 1000005;
const int MOD = 1e9+7;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,ans[M],f[M];char t[M],s[M];
struct Pam
{
int n,last,cnt,fail[M],len[M],ch[M][26];
int dif[M],top[M];
Pam()
{
fail[0]=1;len[1]=-1;
cnt=1;
}
int get_fail(int x)
{
while(s[n-len[x]-1]^s[n])
x=fail[x];
return x;
}
void ins()
{
n++;
int p=get_fail(last),c=s[n]-'a';
if(!ch[p][c])
{
len[++cnt]=len[p]+2;
int tmp=get_fail(fail[p]);
fail[cnt]=ch[tmp][c];
ch[p][c]=cnt;
dif[cnt]=len[cnt]-len[fail[cnt]];
top[cnt]=(dif[cnt]==dif[fail[cnt]])?top[fail[cnt]]:fail[cnt];
}
last=ch[p][c];
}
void dp(int id)
{
for(int i=last;i;i=top[i])
{
f[i]=ans[id-len[top[i]]-dif[i]];
if(top[i]^fail[i])
f[i]=(f[i]+f[fail[i]])%MOD;
if(!(id&1))
ans[id]=(ans[id]+f[i])%MOD;
}
}
}A;
int main()
{
scanf("%s",t+1),n=strlen(t+1);
if(n&1)
{
puts("0");
return 0;
}
for(int i=1;i<=n;i+=2) s[i]=t[(i+1)/2];
for(int i=2;i<=n;i+=2) s[i]=t[n-i/2+1];
ans[0]=1;
for(int i=1;i<=n;i++)
{
A.ins();
A.dp(i);
}
printf("%d\n",ans[n]);
}