题意:
给你一个字符串,问有多少种划分,使得如果划分后有k个部分,则sub[i] = sub[k - i + 1],s[i]是第i个部分代表的串,k必须是偶数。
题解:
普通的回文串是s[1]对应s[n],s[2]对应s[n - 1].....这里有不同,是s[1] 对应 s[n - j], s[2]对应s[n - j + 1],注意到现在对应关系是双方下标都在递增,而普通回文串是第一个下标增加 ,第二个下标减小。到这里我们可以想到把字符串倒过来一下,对于这题我们应该把字符串变成s[1]s[n]s[2]s[n - 1]s[3]s[n - 2]......然后问题就被转换了,现在相当于求当前串划分成一些偶长度的串,使得每个串都是回文串的方案数。
然后就可以用回文树和DP来做了。
可是现在还是有问题。假如沿着fail链DP,是n ^ 2的,为什么呢?
如果串是aaaaaaaa.....那么每个fail链长度都是O(n)的。
所以我们就要用到下面这些性质了:
1.一个回文串S的后缀T如果是回文串等价于T是S的border
2.对于若干个回文后缀,如果长度都大于len/2,那么它们构成了一个等差数列
3.将一个串S的所有border按长度从小到大排序后,能形成log个等差数列
第三条可以通过第二条推导过来,为什么呢?当前长度len有一个等差数列, len / 2 又有一个, len / 2 / 2 又有.....那么就是log个等差数列。
我们用f[i]记录[1...i]时的方案数,g[i]记录以回文串i为最大长度的等差数列的当前f值和。
更新g[p]的时候,假如当前这个等差数列是a1, a2, a3 。长度a1 > a2 > a3, 根据回文串的对称性,实际上f[i - a1], f[i - a2]都在g[fail[p]]里面算过了,只要加上f[i - a3]即可。
然后如果i是偶数f[i]才加上g[p]。
复杂度是nlogn。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <bitset>
#include <map>
#include <vector>
#include <stack>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<<endl;
#else
#define debug(x) 1;
#endif
#define chmax(x,y) x=max(x,y)
#define chmin(x,y) x=min(x,y)
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, int> pii;
const ll MOD = 1e9 + 7;
const double eps = 1e-10;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 1e6 + 5;
char s[MAXN];
char str[MAXN];
ll f[MAXN];
ll g[MAXN];
const int N = 26 ;
/*
1.一个回文串S的后缀T如果是回文串等价于T是S的border
2.对于若干个回文后缀,如果长度都大于len/2,那么它们构成了一个等差数列
3.将一个串S的所有border按长度从小到大排序后,能形成log个等差数列
*/
struct Palindromic_Tree {
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。
int dif[MAXN];
int id[MAXN];
int go[MAXN];
int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}
void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}
void add ( int c ) {
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
dif[now] = len[now] - len[fail[now]];
go[now] = dif[now] == dif[fail[now]] ? go[fail[now]] : fail[now];
}
id[n] = last = next[cur][c] ;
cnt[last] ++ ;
}
//abbacddc
void solve() {
for (int i = 1; i <= n; i++) {
int idx = id[i];
int cnt = 0;
for (int j = idx; j; j = go[j]) {
cnt++;
g[j] = f[i - len[go[j]] - dif[j]];
if (dif[j] == dif[fail[j]]) g[j] = (g[j] + g[fail[j]]) % MOD;
if(i % 2 == 0) f[i] = (f[i] + g[j]) % MOD;
}
}
}
} PAM;
int main() {
#ifdef LOCAL
freopen ("input.txt", "r", stdin);
#endif
PAM.init();
scanf ("%s", s + 1);
int n = strlen (s + 1);
if (n & 1) return 0 * puts ("0");
int cnt = 0;
for (int i = 1; i <= n / 2; i++) {
str[++cnt] = s[i];
str[++cnt] = s[n - i + 1];
}
for (int i = 1; i <= n; i++) PAM.add (str[i] - 'a');
f[0] = 1;
PAM.solve();
printf("%lld\n", f[n]);
return 0;
}

本文介绍了一种解决特定回文串划分问题的高效算法。该问题要求将输入字符串以特定方式划分为多个子串,使得所有子串均为偶长且构成回文结构。通过巧妙地重新组织输入字符串并利用回文树和DP技术,该算法能在nlogn的时间复杂度内求解问题。
1113

被折叠的 条评论
为什么被折叠?



