[加强版] Codeforces 835D Palindromic characteristics (回文自动机、DP)

题目链接: https://codeforces.com/contest/835/problem/D

题意: 一个回文串是\(1\)-回文的,如果一个回文串的左半部分和右半部分一样且都是\(k\)-回文串(右半部分是指长度为该串长度除以二下取整的后缀),则该串为\((k+1)\)回文串,满足该串是\(k\)回文串的最大\(k\)称作该串的回文级别。给定一个串\(s\), 对于每一个\(k=1,2,...,n\)求出该串中有多少个位置不同的\(k\)-回文串。\(n\le 5\times 10^6\). (除数据范围外与原题均相同)

题解: \(n^2\)的做法肯定是以子串为状态进行dp, 但是显然废状态太多了,只有回文串dp值非零,而一个串本质不同的回文串的个数是\(O(n)\)的。

所以,以本质不同的回文串作为状态进行dp. 本质不同的回文串一一对应回文自动机上的节点。设\(f[x]\)表示\(x\)节点代表回文串的回文级别。

然后转移方程显然: 若\(x\)不存在长度为\([\frac{x}{2}]\)(中括号表示下取整)的回文后缀,则\(dp[x]=1\), 否则\(dp[x]\)等于那个回文后缀的\(dp\)值+1.

dp完之后,我们求的是本质不同,但是题目要求求位置不同,所以还需要再统计一下每个点的子树大小。

时间复杂度\(O(n)\).

代码 (Codeforces 835D AC)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define llong long long
using namespace std;

const int N = 1e6+2;
const int LGN = 23;
const int S = 26;
int son[N+3][S+1];
int fail[N+3];
int len[N+3];
int sz[N+3];
int tsz[N+3];
char a[N+3];
int dp[N+3];
int bd[N+3];
int ord[N+3];
int buc[N+3];
llong ans[N+3];
int n,siz,lstpos;

void initPAM()
{
    siz = lstpos = 1; fail[0] = fail[1] = 1; len[1] = -1; len[0] = 0; bd[0] = 0; bd[1] = 1;
}

void insertchar(int id)
{
    int p = lstpos;
    while(a[id]!=a[id-len[p]-1]) {p = fail[p];}
    if(!son[p][a[id]])
    {
        siz++; int u = siz,v = fail[p];
        while(a[id]!=a[id-len[v]-1]) {v = fail[v];}
        fail[u] = son[v][a[id]]; son[p][a[id]] = u; len[u] = len[p]+2;
        if(len[u]<=2) {bd[u] = fail[u];}
        else
        {
            bd[u] = bd[p];
            while(a[id]!=a[id-len[bd[u]]-1] || (len[bd[u]]+2)*2>len[u])
            {
                bd[u] = fail[bd[u]];
            }
            bd[u] = son[bd[u]][a[id]];
        }
        if(len[bd[u]]==(len[u]>>1)) {dp[u] = max(0,dp[bd[u]])+1;}
        else {dp[u] = 1;}
    }
    sz[son[p][a[id]]]++;
    lstpos = son[p][a[id]];
}

int main()
{
    initPAM();
    scanf("%s",a+1); n = strlen(a+1);
    for(int i=1; i<=n; i++) a[i]-=96;
    for(int i=1; i<=n; i++) {insertchar(i);}
    for(int i=2; i<=siz; i++) {buc[len[i]]++;}
    for(int i=1; i<=n; i++) {buc[i] += buc[i-1];}
    for(int i=siz; i>=2; i--) {ord[(buc[len[i]]--)+1] = i;}
    ord[0] = 1; ord[1] = 0;
    for(int j=siz; j>=2; j--)
    {
        int u = ord[j];
        sz[fail[u]] += sz[u];
    }
    for(int j=2; j<=siz; j++)
    {
        ans[dp[j]] += (llong)sz[j];
    }
    for(int j=LGN-1; j>=1; j--) ans[j] += ans[j+1];
    for(int i=1; i<=n; i++) printf("%I64d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值