@description@

给定一个字符串 s,求有多少种方案可将其划分成偶数个段 \(p_1, p_2, ..., p_k\),使得 \(p_i = p_{k-i+1}\)。

模 10^9 + 7。

2 ≤ |s| ≤ 10^6。

原题戳这里

@solution@

回文划分有一个经典的做法。不过这道题并不完全是回文划分,需要进一步地转化。

构造串 t = \(s_1s_ns_2s_{n-1}...\),则对 t 的偶回文划分对应了原题的一个合法划分。

定义 dp[i] 表示前 i 个字符进行回文划分的方案数。因为要偶回文划分,所以假如 i 为奇数直接令 dp[i] = 0。

朴素的转移即构建回文自动机,求出以 i 为结束的所有回文串,然后把这些回文串进行转移。

考虑是否可以利用之前已经处理过的信息。如图:

假如有若干回文串的长度形成以 d 为公差的等差数列(如图黑色线段),我们可以取 i-d 已经计算过的值(如图蓝色线段)方便我们转移。

具体一点,对于回文自动机上每一个结点 x,记 d(v) = len(v) - len(fa(v)),将 d(v) 相同的连续段称作一个等差数列(注意这里等差数列的定义)。

点 x 的最后更新位置 t 指的是,在前缀 t 所对应的最长回文后缀到根的路径中,x 是某一个等差数列中深度最大的点。

现在在回文树上的每个点维护:以他结尾的等差数列在其最后更新位置上的对应 dp 的和 f。

一个结论:若 d(v) = d(fa(v)),则 i - d(v) 是 fa(v) 的最后更新位置。

证明有两部分:首先证明没有 (i - d(v), i) 的位置更新 fa(v),然后再证明在 i - d(v) 这个地方 fa(v) 必定被更新。

因此直接取 fa(v) 维护好的 f 就是刚刚图中蓝色部分。

注意黑色部分还有一个回文串蓝色部分没有(slack[i] 上面那个串),需要单独拿出来处理。

另一个结论:一个回文串的所有回文串形成 O(log) 个等差数列。

回文后缀即回文串的 border,因此直接用 border 与周期的关系证明即可。

维护向上第一个 d(x) 与当前这个 d(v) 不相等的点,记 slack(v) = x。这样直接跳 slack 最多跳 log 次就到根了。

注意一点细节:当一个点的 fa = slack 时,它的 fa 不应该被算作贡献。

@accepted code@

#include <cstdio>
#include <cstring>
const int MAXN = 1000000;
const int MOD = int(1E9) + 7;
struct node{
int len, dif, f;
node *slk, *ch[26], *fa;
}pl[MAXN + 5], *rt1, *rt2, *ncnt;
node *nd[MAXN + 5];
void build(char *S, int len) {
rt1 = ncnt = pl, rt2 = (++ncnt);
rt2->fa = rt1, rt1->len = -1, rt2->len = 0;
node *pre = rt1;
for(int i=0;i<len;i++) {
while( S[i] != S[i - pre->len - 1] )
pre = pre->fa;
if( pre->ch[S[i] - 'a'] == NULL ) {
node *q = (++ncnt);
q->len = pre->len + 2;
if( pre == rt1 )
q->fa = rt2;
else {
node *p = pre->fa;
while( S[i] != S[i - p->len - 1 ] )
p = p->fa;
q->fa = p->ch[S[i] - 'a'];
}
q->dif = q->len - q->fa->len;
q->slk = (q->dif == q->fa->dif ? q->fa->slk : q->fa);
pre->ch[S[i] - 'a'] = q;
}
nd[i+1] = pre = pre->ch[S[i] - 'a'];
}
}
char s[MAXN + 5], t[MAXN + 5];
int dp[MAXN + 5];
int main() {
scanf("%s", s);
int len = strlen(s);
for(int i=0;i<len/2;i++)
t[(i<<1) + 1] = s[i], t[(i<<1|1) + 1] = s[len-i-1];
build(t + 1, len);
dp[0] = 1;
for(int i=1;i<=len;i++) {
node *p = nd[i];
while( p != rt2 ) {
p->f = dp[i - p->slk->len - p->dif];
if( p->slk != p->fa )
p->f = (p->f + p->fa->f) % MOD;
dp[i] = (dp[i] + p->f) % MOD;
p = p->slk;
}
if( i & 1 ) dp[i] = 0;
}
printf("%d\n", dp[len]);
}

@details@

关键的代码不是很长。

一开始本来想写分奇偶回文串讨论着做,发现有点难搞。。。后来才发现可以直接把 dp 值强制改成 0。。。

@codeforces - 932G@ Palindrome Partition的更多相关文章

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

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

  2. 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 ...

  3. LeetCode: Palindrome Partition

    LeetCode: Palindrome Partition Given a string s, partition s such that every substring of the partit ...

  4. Codeforces 486C Palindrome Transformation(贪心)

    题目链接:Codeforces 486C Palindrome Transformation 题目大意:给定一个字符串,长度N.指针位置P,问说最少花多少步将字符串变成回文串. 解题思路:事实上仅仅要 ...

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

    [CF932G]Palindrome Partition(回文树,动态规划) 题面 CF 翻译: 给定一个串,把串分为偶数段 假设分为了\(s1,s2,s3....sk\) 求,满足\(s_1=s_k ...

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

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

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

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

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

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

  9. Leetcode: Palindrome Partition I II

    题目一, 题目二 思路 1. 第一遍做时就参考别人的, 现在又忘记了 做的时候使用的是二维动态规划, 超时加超内存 2. 只当 string 左部分是回文的时候才有可能减少 cut 3. 一维动规. ...

随机推荐

  1. MyBatis配置文件(一)――properties属性

    MyBatis配置文件中有很多配置项,这些配置项分别代表什么,有什么作用,需要理一下了.先通过下面这个例子来看都有哪些配置项 <?xml version="1.0" enco ...

  2. 写一个网页进度loading

    作者:jack_lo www.jianshu.com/p/4c93f5bd9861 如有好文章投稿,请点击 → 这里了解详情 loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在 ...

  3. 转载:JVM内存分代策略

    Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代.老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略. 为什么要分代? 堆内存是虚拟机管理的内存中最大的一 ...

  4. C#通过鼠标点击panel移动来控制无边框窗体移动

    Point point = , -); bool mouseDown = false; private void panelEx5_MouseDown(object sender, MouseEven ...

  5. spring-搭建-概念-配置详解-属性注入

    1 spring介绍  三层架构中spring位置 spring一站式框架 正是因为spring框架性质是属于容器性质的. 容器中装什么对象就有什么功能.所以可以一站式. 不仅不排斥其他框架,还能帮其 ...

  6. 2019-7-2-Roslyn-开发-NuGet-包的-Task-编译可能遇到的问题

    title author date CreateTime categories Roslyn 开发 NuGet 包的 Task 编译可能遇到的问题 lindexi 2019-07-02 10:43:2 ...

  7. Promise的源码实现(符合Promise/A+规范)

    我们手写一个Promise/A+规范,然后安装测试脚本,以求通过这个规范. //Promise/A+源代码 // new Promise时,需要传递一个executor执行器,执行器立即执行 // e ...

  8. LA4254 Processor

      题意:有n个任务,每个任务有三个参数ri,di和wi,表示必须在时刻[ri,di]之内执行,工作量为wi.处理器执行速度可以变化,当执行速度为s时,工作量为wi.处理器的速度可以变化,当执行速度为 ...

  9. 点击按钮使用window.open打开页面后,再次点击按钮会再打开一个页面,如何解决?

    点击按钮使用window.open打开页面后,再次点击按钮会再打开一个页面,如何解决? window.open("page1.html","win1"); 这句 ...

  10. window 导入sql 防止乱码

    第一步:创建数据库 create database if not exists db_news default charset utf8 collate utf8_general_ci; 第二步:设置 ...