Codeforces 932G Palindrome Partition dp+回文树

博客讲述了如何解决Codeforces上的一道题目932G,该题要求将一个偶数长度的字符串按照特定条件划分成若干部分,每部分形成回文串。博主分析了直接解法的不可行性,并提出通过构造回文串和利用回文树优化的方法,将问题转化为求解偶数长度回文串的划分方案数。讨论了回文串的性质,特别是回文串的border与等差数列的关系,并描述了如何维护回文树节点的等差数列信息以优化算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

给出一个长度为偶数的字符串S,要求把S分成k部分,其中k为任意偶数,设为a[1..k],且满足对于任意的i,有a[i]=a[k-i+1]。问划分的方案数。
n<=1000000

分析

刚了一个下午的题,感觉学到了很多新东西。
首先直接做显然不可做,考虑把S变成S[1]S[n]S[2]S[n-1]…S[n/2]S[n/2+1]的形式,不难发现对于原来划分方案中的a[i]和a[k-i+1],它们在新串中恰好连续且组成一个回文串。那么问题就变成了把新串划分成若干部分,使得每一部分都是偶回文串,问方案。
一开始的想法是把回文树建出来,然后设f[i]表示把前i个字符划分的方案数,然后枚举i的每一个回文后缀进行转移。
这样子的复杂度是O(回文串数量)的,显然会超时,考虑如何优化。
有一个小结论就是一个回文串S的一个后缀T是回文串当且仅当T是S的border。
有一个性质就是一个串的所有border可以划分成O(log)段等差数列。
证明的话,可以考虑对于所有长度大于n/2的border,它们显然组成一个等差数列,然后把长度减半,如此类推,那么等差数列的数量就只有O(log)个。
对于回文树上的每个节点p,我们可以维护以它作为等差数列末项时每一项的f值之和g[p],维护nxt[p]表示节点p下一个等差数列的末项。
怎么维护呢?设当前节点为末项的等差数列有b1,b2,b3,其中b1>b2>b3。那么有g[p]=f[i-b1]+f[i-b2]+f[i-b3]。
根据回文串的性质,不难发现S[i-b2,i-d]=S[i-b3,i],S[i-b1,i-d]=S[i-b2,i],那么在g[fail[p]]中就已经包含了f[i-b1]和f[i-b2],只要把f[i-b3]加上就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1000005;
const int MOD=1000000007;

int n,last,sz,len[N],fail[N],ch[N][26],id[N],f[N],diff[N],nxt[N],g[N];
char str[N],s[N];

int extend(int x,int n)
{
    int p=last;
    while (s[n-len[p]-1]!=s[n]) p=fail[p];
    if (!ch[p][x])
    {
        int now=++sz,k=fail[p];len[now]=len[p]+2;
        while (s[n-len[k]-1]!=s[n]) k=fail[k];
        fail[now]=ch[k][x];ch[p][x]=now;
        diff[now]=len[now]-len[fail[now]];
        if (diff[now]==diff[fail[now]]) nxt[now]=nxt[fail[now]];
        else nxt[now]=fail[now];
    }
    last=ch[p][x];return last;
}

int main()
{
    scanf("%s",str+1);
    n=strlen(str+1);
    int now=0;
    for (int i=1;i<=n;i+=2) s[i]=str[++now];
    now=n+1;
    for (int i=2;i<=n;i+=2) s[i]=str[--now];
    fail[0]=1;len[1]=-1;sz=1;
    for (int i=1;i<=n;i++) id[i]=extend(s[i]-'a',i);
    f[0]=1;
    for (int i=1;i<=n;i++)
        for (int p=id[i];p;p=nxt[p])
        {
            g[p]=f[i-(len[nxt[p]]+diff[p])];
            if (diff[p]==diff[fail[p]]) (g[p]+=g[fail[p]])%=MOD;
            if (!(i&1)) (f[i]+=g[p])%=MOD;
        }
    printf("%d",f[n]);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值