【CF932G】Palindrome Partition(回文树,动态规划)

题面

CF

翻译:

给定一个串,把串分为偶数段

假设分为了\(s1,s2,s3....sk\)

求,满足\(s_1=s_k,s_2=s_{k-1}......\)的方案数

题解

反正我是不会做

基本就是照着\(laofu\)的打了一遍(laofu太强啦)

这题分成了两个步骤

如果直接分\(k\)段我们是没法直接判断的

假设两段\(s_i,s_{k-i+1}\)

因为\(s_i=s_{k-i+1}=x_1x_2.....x_j\)

假设\(s_i\)的开始位置为\(p\)

假设原串\(S\)的长度为\(n\)

\(s_i=S[p]S[p+1]....S[p+j-1]\)

\(s_{k-i+1}=S[n-j-p+1]S[n-j-p+2]...S[n-p+1]\)

对应相等的关系是

\(S[p]=S[n-p-j+1]\)

如果\(p=1\)考虑一下特殊情况

那么就是\(S[1]=S[n-j]\)

那么,如果我们有一个串\(S'\)

\(S'=S[1]S[n]S[2]S[n-2].....\)

那么,对应到上面的相等关系里面,

就是\(S'[1..j]\) 是一个回文串

其他的每一组对应关系也是如此

所以题目转换成了:

告诉你了\(S'\)回答把\(S'\)分为若干个长度为偶数的回文串的方案数

(如果没有搞清楚可以手玩一下)

这个就是一个裸的\(dp\)了

设\(f[i]\)表示\(S'[1..i]\)划分为若干长度为偶数的回文串的方案数

得到转移方程:

\[f[i]=\sum_{j}f[j-1]
\]

其中,\(S'[j,i]\)是回文串

那么,每一个\(j\)对应的位置相当于是\(S'[1..i]\)的回文后缀

构建出回文树之后沿着\(last\)跳\(fail\)就可以得到所有的\(j\)的位置

然后我们就写出来了一份代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 1000100
#define MOD 1000000007
inline int read()
{
RG int x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ch[MAX],s[MAX];
int n,f[MAX];
struct Palindromic_Tree
{
struct Node
{
int son[26];
int ff,len;
}t[MAX];
int last,tot;
void init()
{
t[tot=1].len=-1;
t[0].ff=t[1].ff=1;
}
void extend(int c,int n,char *s)
{
int p=last;
while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
if(!t[p].son[c])
{
int v=++tot,k=t[p].ff;
t[v].len=t[p].len+2;
while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
t[v].ff=t[k].son[c];
t[p].son[c]=v;
}
last=t[p].son[c];
}
}PT;
int main()
{
PT.init();
scanf("%s",ch+1);
n=strlen(ch+1);
if(n&1){puts("0");return 0;}
for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
reverse(&ch[1],&ch[n+1]);
for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
f[0]=1;
for(int i=1;i<=n;++i)
{
PT.extend(s[i]-97,i,s);
int p=PT.last;
while(p!=1)
{
if(PT.t[p].len%2==0&&PT.t[p].len>0)f[i]=(f[i]+f[i-PT.t[p].len])%MOD;
p=PT.t[p].ff;
}
}
printf("%d\n",f[n]);
return 0;
}

然后在\(CF\)上交一发:

看一看数据是啥呢?

如果我们像这份代码一样,沿着\(fail\)一路上跳

因为所有字符都相同

所以要跳\(O(n)\)次

复杂度变为了\(O(n^2)\)

完美\(TLE\)

所以肯定就不能这么做。。。

然后我就不会了


假设对于某个位置,它对应的若干回文后缀

如果他们的长度\(>len/2\)

可以证明他们的长度等差

证明?根据回文串的性质,上面的那些圈圈都是相等的串

证毕

如果把这一些看成一组

每一组都至少要\(÷ 2\)

所以至多只会有\(log\)组

这样能够保证复杂度,所以我们考虑能否一组一组转移

设\(g[x]\)表示这一组东西的和

因为一组不能直接在串中表示

所以用回文树上的节点来表示

所以,\(g[x]\)表示从节点\(x\)开始,一直到缩短的值不再是当前这个等差的位置产生的贡献

比如对于这一组,它产生的贡献就是

\(g[x]=f[j1]+f[j2]+f[j3]\)

(最底下那个是原串)

好的,假设我们当前位置是\(i\)

也就是最底下的原串是\(S'[1..i]\)

最长的回文后缀,也就是当前回文树上的\(last\)

令\(p=last\)

因为是最长的一个,所以之前肯定不会计算过这个位置的值

所以\(g[p]=f[j_1]\)

然后往上面的那些回文后缀看

这些红色的代表着关于下面的父亲对称的回文前缀

我们惊奇的发现:

它们的开始位置怎么就这么巧呢?

正好就是我要算的东西

也就是说\(g[p]+=g[p.fail]\)

当然,前提是\(p.fail\)还在这一组等差的串里面

然后\(p\)就跳到第一个不是它所在的等差的回文后缀的位置

所以\(f[i]=\sum_{p}g[p]\),前提是\(i\%2=0\)

最后,总结一下算法

对于给定的串\(S\)

把它变成\(S'=S[1]S[n]S[2]S[n-1].....\)

然后依次构建回文树

每个节点要记录一下它所在的这个等差的回文后缀在哪个节点结束

然后对于每次的\(last\)节点向上跳

跳到第一个不是自己这一段回文的位置

因为每次至少要\(÷2\),所以至多跳\(log\)次

综上,时间复杂度\(O(nlog)\)

空间复杂度\(O(n\sum)\),\(\sum\)代表字符集大小

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 1000100
#define MOD 1000000007
inline int read()
{
RG int x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
char ch[MAX],s[MAX];
int n,anc[MAX],diff[MAX];
int ans[MAX],f[MAX];
struct Palindromic_Tree
{
struct Node
{
int son[26];
int ff,len;
}t[MAX];
int last,tot;
void init()
{
t[tot=1].len=-1;
t[0].ff=t[1].ff=1;
anc[0]=1;
}
void extend(int c,int n,char *s)
{
int p=last;
while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
if(!t[p].son[c])
{
int v=++tot,k=t[p].ff;
t[v].len=t[p].len+2;
while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
t[v].ff=t[k].son[c];
t[p].son[c]=v;
diff[v]=t[v].len-t[t[v].ff].len;
anc[v]=(diff[v]==diff[t[v].ff])?anc[t[v].ff]:t[v].ff;
}
last=t[p].son[c];
}
}PT;
int main()
{
PT.init();
scanf("%s",ch+1);
n=strlen(ch+1);
if(n&1){puts("0");return 0;}
for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
reverse(&ch[1],&ch[n+1]);
for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
ans[0]=1;
for(int i=1;i<=n;++i)
{
PT.extend(s[i]-97,i,s);
for(int k=PT.last;k;k=anc[k])
{
f[k]=ans[i-PT.t[anc[k]].len-diff[k]];
if(anc[k]!=PT.t[k].ff)
f[k]=(f[k]+f[PT.t[k].ff])%MOD;
if(!(i&1))ans[i]=(ans[i]+f[k])%MOD;
}
}
printf("%d\n",ans[n]);
return 0;
}

【CF932G】Palindrome Partition(回文树,动态规划)的更多相关文章

  1. Codeforces 932G Palindrome Partition - 回文树 - 动态规划

    题目传送门 通往???的传送点 通往神秘地带的传送点 通往未知地带的传送点 题目大意 给定一个串$s$,要求将$s$划分为$t_{1}t_{2}\cdots t_{k}$,其中$2\mid k$,且$ ...

  2. CF932G Palindrome Partition(回文自动机)

    CF932G Palindrome Partition(回文自动机) Luogu 题解时间 首先将字符串 $ s[1...n] $ 变成 $ s[1]s[n]s[2]s[n-1]... $ 就变成了求 ...

  3. Codeforces 932G Palindrome Partition 回文树+DP

    题意:给定一个串,把串分为偶数段 假设分为$s_1,s_2,s_3....s_k$ 求满足$ s_1=s_k,s_2=s_{ k-1 }... $的方案数模$10^9+7$ $|S|\leq 10^6 ...

  4. HDU 6599 I Love Palindrome String (回文树+hash)

    题意 找如下子串的个数: (l,r)是回文串,并且(l,(l+r)/2)也是回文串 思路 本来写了个回文树+dfs+hash,由于用了map所以T了 后来发现既然该子串和该子串的前半部分都是回文串,所 ...

  5. 【CF932G】Palindrome Partition 回文自动机

    [CF932G]Palindrome Partition 题意:给你一个字符串s,问你有多少种方式,可以将s分割成k个子串,设k个子串是$x_1x_2...x_k$,满足$x_1=x_k,x_2=x_ ...

  6. 2019牛客暑期多校训练营(第六场)Palindrome Mouse 回文树+dfs

    题目传送门 题意:给出一个字符串,将字符串中所有的回文子串全部放入一个集合里,去重后.问这个集合里有几对<a,b>,使得a是b的子串. 思路:一开始想偏了,以为只要求每个回文串的回文后缀的 ...

  7. Palindrome Partition CodeForces - 932G 回文树+DP+(回文后缀的等差性质)

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

  8. Palindrome Mouse(2019年牛客多校第六场C题+回文树+树状数组)

    目录 题目链接 题意 思路 代码 题目链接 传送门 题意 问\(s\)串中所有本质不同的回文子串中有多少对回文子串满足\(a\)是\(b\)的子串. 思路 参考代码:传送门 本质不同的回文子串肯定是要 ...

  9. 2019牛客暑期多校训练营(第六场)C:Palindrome Mouse(回文树+树剖)

    题意:给定字符串Str,求出回文串集合为S,问S中的(a,b)满足a是b的子串的对数. 思路:开始和题解的思路差不多,维护当前后缀的每个串的最后出现位置,但是不知道怎么套“最小回文分割”,所以想到了树 ...

随机推荐

  1. URL中特殊符号的处理

    问题描述 我们在对接第三方系统的时候通常需要get或post来传输数据,但此时如果参数中存在&% #*!包括空格等特殊符号的时候就无法正常请求具体表现在参数获取不正确或者获取不到参数,甚至有时 ...

  2. mysqldump 备份导出数据排除某张表

    就用 --ignore-table=dbname.tablename参数就行,可以忽略多个. /usr/bin/mysqldump -- -uroot -p123456 dbname --ignore ...

  3. Mac通过brew安装reds、memcached

    redis brew install php70-redis 配置文件: /usr/local/etc/php/7.0/conf.d/ext-redis.ini memcached brew inst ...

  4. 【动画】JQuery实现冒泡排序算法动画演示

    1 前言 冒泡排序是大家最熟悉的算法,也是最简单的排序算法,因其排序过程很象气泡逐渐向上漂浮而得名.为了更好的理解其基本的思想,毛三胖利用JQuery实现了冒泡排序的动画演示,并计划陆续实现其它排序算 ...

  5. 【剑指offer28:字符串的排列】【java】

    题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. import ja ...

  6. 【阿里聚安全·安全周刊】科学家警告外星恶意代码|新方法任意解锁iPhone

    本周的七个关键词: 外星恶意代码 丨 任意解锁iPhone 丨  安卓9.0 丨 黑客攻击医疗设备 丨 仙女座僵尸网络 丨  苹果联合创始人被骗比特币 丨JavaScript -1-   [恶意代码] ...

  7. 彻底解决Yii2中网页刷新时验证码不刷新的问题

    修改vendor/yiisoft/yii2/captcha/CaptchaValidator.php这个文件就可以了,修改的地方见下图: 总结 归根到底,是因为yii2在渲染网页的时候,会先输出js验 ...

  8. FFMpeg在Ubuntu上的安装和使用

    在Ubuntu Server上编译FFmpeg FFmpeg是最流行的开源视频转码工具包,在Ubuntu上可以直接通过apt-get安装,但是默认的编码器不提供x264这些non-free的编码器,所 ...

  9. FreeImage库如何转换图片格式?

    FreeImage下载地址:http://freeimage.sourceforge.net/ //freeimagemain.h #ifndef FREEIMAGEMAIN_H #define FR ...

  10. hadoop/storm以及hive/hbase/pig区别整理

    STORM与HADOOP的比较 对于一堆时刻在增长的数据,如果要统计,可以采取什么方法呢? 等数据增长到一定程度的时候,跑一个统计程序进行统计.适用于实时性要求不高的场景.如将数据导到HDFS,再运行 ...