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]);
}

posted @ 2020-12-27 16:52  C202044zxy  阅读(198)  评论(0编辑  收藏  举报