题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6599
题目大意:给你一个串让你求
s
[
l
,
r
]
s[l,r]
s[l,r]是回文串且
s
[
l
,
l
+
r
2
]
s[l,\frac{l + r}{2}]
s[l,2l+r]也是回文串的子串数目。
输出答案时要按长度划分答案,也就是按r - l + 1划分答案然后依次输出所有长度的满足条件的子串数目。
题解:裸板题,用回文树求出所有不同质的回文串的数量,然后对于每一种回文串,check一下判断满不满足题目要求即可,check可以用字符串hash也可以用马拉车跑出半径来check。
回文树 + 字符串hash
#include<bits/stdc++.h>
using namespace std;
const int maxn = 6e5 + 10;
char tmp[maxn],str[maxn];
typedef long long ll;
typedef unsigned long long ull;
const ull h1 = 201326611;
const ull h2 = 50331653;
ull h[maxn], p[maxn];
ll ans[maxn];
ull Hash(int l, int r) {
if (l == 0) return h[r];
return h[r] - h[l - 1] * p[r - l + 1];
}
bool check(int l, int r) {
int len = r - l + 1;
int mid = (l + r) >> 1;
if (len & 1) return Hash(l, mid) == Hash(mid, r);
else return Hash(l, mid) == Hash(mid + 1, r);
}
struct Palindromic_tree {
int nxt[maxn][28];
int len[maxn];
int num[maxn];
int cnt[maxn];
int fail[maxn];
int s[maxn];
int id[maxn];
int last;
int p;
int n;
int newnode(int length) {
for(int i = 0; i < 28; i++) nxt[p][i] = 0;
num[p] = cnt[p] = 0;
len[p] = length;
return p++;
}
void init() {
n = 0;
p = 0;
last = 0;
newnode(0);
newnode(-1);
s[n] = -1;
fail[0] = 1;
}
int get_fail(int x) {
while(s[n - len[x] - 1] != s[n]) x = fail[x];
return x;
}
void add(int c) {
c -= 'a';
s[++n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !nxt[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = nxt[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
nxt[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = nxt[cur][c] ;
id[last] = n;
cnt[last]++ ;
}
void count() {
for(int i = p - 1; i >= 0; i--) cnt[fail[i]] += cnt[i];
for(int i = 2; i < p; i++) {
if(check(id[i] - len[i],id[i] - 1)) {
ans[len[i]] += cnt[i];
}
}
}
}t;
int main() {
p[0] = 1;
for(int i = 1; i < maxn; i++)
p[i] = h1 * p[i - 1];
while(~scanf("%s",str)) {
memset(ans,0,sizeof ans);
int l = strlen(str);
t.init();
for(int i = 0; i < l; i++)
t.add(str[i]);
h[0] = str[0];
for(int i = 1; i < l; i++)
h[i] = h[i - 1] * h1 + str[i];
t.count();
for(int i = 1; i <= l; i++) {
if(i - 1) printf(" ");
printf("%lld",ans[i]);
}
printf("\n");
}
return 0;
}
(标程给的是马拉车+回文树,思路差不多,但自己写了一发就是RE,遂放弃)。
回文树(待刷):
回文树是一种类似AC自动机的玩意,有fail指针,类似trie的树形结构,O(n)的空间,每一个结点代表一种不同质的回文串,最多 n 种不同质的回文串。板子就是上面一坨。
它的大致思想和过程见大佬博客:https://blog.csdn.net/lwfcgz/article/details/48739051
其实不停的找失配指针是一个有点抽象的东西,因为过程看起来是在不停的找最长回文后缀,实际过程是在树上跳来跳去,每一个fail指针指向的是最长回文后缀(不包括自己)。
参考代码自己按过程画一个图就了解了。
例如 ababa 的回文树构建过程。
推荐几篇大佬博客:
https://blog.csdn.net/u013368721/article/details/42100363 (有图,变量名意义解释)
https://blog.csdn.net/lwfcgz/article/details/48739051 (有算法思想过程图)