[CF932G]Palindrome Partition(PAM回文划分dp)
题面
https://codeforces.com/problemset/problem/932/G
题解
前置知识
在做此题之前,需要了解利用PAM在\(O(n \log n)\)时间内解决最小回文划分、回文划分数等回文划分相关dp的方法(见上面链接)
有了这个基础,我们再做此题。由于"第一个与最后一个相同,第二个与倒数第二个相同……"这样的条件极难dp,所以我们考虑转化题意。
如何做呢?首先如果给出的字符串长度为奇数,那肯定是0啦。
否则的话,将字符串的前后半部分拆开,将后半部分翻转,再把它们“拉链式”合并。以本题样例1为例:
就成功地将原题那个恶心的条件变成了求回文串划分方案数。样例1的答案ab|cd|cd|ab对应着新序列的abba|cddc。
另外有一个地方与之前的回文划分dp不同,就是这里的回文划分中的每一个回文串长度必须是偶数。不过这个也好解决,dp时将所有奇数位的dp值都设成0就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rg register
#define In inline
const int N = 1e6;
const ll mod = 1e9 + 7;
namespace ModCalc{
In void Inc(ll &x,ll y){
x += y;if(x >= mod)x -= mod;
}
In void Dec(ll &x,ll y){
x -= y;if(x < 0)x += mod;
}
In ll Add(ll x,ll y){
Inc(x,y);return x;
}
In ll Sub(ll x,ll y){
Dec(x,y);return x;
}
}
using namespace ModCalc;
char t[N+5],s[N+5];
int n;
ll f[N+5],g[N+5],slink[N+5],diff[N+5];
struct PAM{
ll nx[N+5][26],len[N+5],fail[N+5];
int last,cnt;
void clear(){
cnt++;
len[1] = -1;
fail[0] = fail[1] = 1;
}
void extend(char c,int n){
int id = c - 'a';
int p = last;
while(s[n-len[p]-1] != s[n])p = fail[p];
if(!nx[p][id]){
cnt++;
len[cnt] = len[p] + 2;
int q = fail[p];
while(s[n-len[q]-1] != s[n])q = fail[q];
fail[cnt] = nx[q][id];
nx[p][id] = cnt;
diff[cnt] = len[cnt] - len[fail[cnt]];
if(diff[cnt] != diff[fail[cnt]])
slink[cnt] = fail[cnt];
else
slink[cnt] = slink[fail[cnt]];
}
last = nx[p][id];
p = last;
while(p >= 2){
g[p] = f[n-len[slink[p]]-diff[p]];
if(diff[p] == diff[fail[p]])Inc(g[p],g[fail[p]]);
if(!(n & 1))Inc(f[n],g[p]);
p = slink[p];
}
}
}P;
int main(){
scanf("%s",t + 1);
n = strlen(t + 1);
if(n & 1){
puts("0");return 0;
}
P.clear();
ll _n = 0;
for(rg int i = 1;(i << 1) <= n;i++)s[++_n] = t[i],s[++_n] = t[n+1-i];
f[0] = 1;
for(rg int i = 1;i <= n;i++)P.extend(s[i],i);
cout<<f[n]<<endl;
return 0;
}