回文树
前言
回文树用于解决字符串回文问题,个人觉得其更应该被叫做“回文自动机”
第一题的注释会写得很详细,剩下的略写
- 回文树
https://blog.csdn.net/lwfcgz/article/details/48739051
https://blog.csdn.net/u013368721/article/details/42100363
Number of Palindromes - SPOJ - NUMOFPAL
题意
求回文字符串数量
思路
回文树模板题
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000 + 5;
const int inf = 0x3f3f3f3f;
typedef long long ll;
char s[N];
struct Node{
//slink: 后缀链接
//len: 字符串长度
//cnt: 包括其在内的后缀字符串数量
//nxt: nxt指针
int slink, len, cnt;
int nxt[26];
};
struct PalindromicTree{
//tot: 静态分配
//cursuffix: 维护当前后缀
int tot;
int cursuffix;
Node tree[N];
void init(){
//长度为-1的字符串,其是不存在的,代表长度为奇数的字符串的根
tree[0].slink = 0, tree[0].len = -1, tree[0].cnt = 1;
//长度为0的字符串,其是空串,代表长度为偶数的字符串的根
tree[1].slink = 0, tree[1].len = 0, tree[1].cnt = 1;
tot = 2, cursuffix = 1;
initNode(1);
initNode(0);
}
void initNode(int o){
memset(tree[o].nxt, -1, sizeof(tree[o].nxt));
}
void add(int pos){
int idx = s[pos] - 'a';
int cur = cursuffix;
//在cur的后缀链接中找到第一个可以将s[pos]接在其左右的字符串
while(true){
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]) break;
cur = tree[cur].slink;
}
//如果这一字符串原本存在于后缀树中
if(tree[cur].nxt[idx] != -1){
//将维护的cursuffix更新为这一状态节点
cursuffix = tree[cur].nxt[idx];
return;
}
//否则,新建一状态节点
int nxt = tree[cur].nxt[idx] = tot++;
initNode(nxt);
tree[nxt].len = tree[cur].len + 2;
cursuffix = nxt;
//如果长度为1,就不得不手动维护这一新节点的slink信息,使其指向空串
//因为无法通过cur的后缀链接中的nxt信息来更新新节点的slink信息
if(tree[nxt].len == 1){
tree[nxt].cnt = 1;
tree[nxt].slink = 1;
return;
}
//如果长度大于1,就通过cur的后缀链接中第一个可以将s[pos]接在左右的字符串状态节点的nxt的节点
//来更新新节点的slink信息
//这一做法的正确性在于,假设当前cur节点的字符串为A,第一个满足条件的后缀链接节点代表的字符串为B,s[pos]为x
//则xBx为xAx的后缀,又因为xAx是回文字符串,因此xBx也是xAx的前缀,那么根据循环顺序,xBx一定比xAx先处理到,
//因此,xBx所代表的字符串节点一定是存在的,也一定是最接近于xAx的
while(true){
cur = tree[cur].slink;
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]){
tree[nxt].slink = tree[cur].nxt[idx];
break;
}
}
//更新这一后缀包含多少个回文字符串
tree[nxt].cnt = tree[tree[nxt].slink].cnt + 1;
}
};
PalindromicTree pt;
int main(){
pt.init();
ll ans = 0;
scanf("%s", s);
for(int i = 0; s[i]; i++){
pt.add(i);
ans += pt.tree[pt.cursuffix].cnt;
}
printf("%lld\n", ans);
}
最长双回文串 - HYSBZ - 2565
思路
首先,跑一遍回文树,用数组l记录下以每个位置为右端点的回文字符串最长能是多长
然后将字符串翻转,再跑一遍回文树,用数组r记录下以每个位置为左端点的回文字符串最长能是多长
最后遍历一遍数组,取出l[i] + r[i + 1]的最大值
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
typedef long long ll;
char s[N];
int l[N], r[N];
struct Node{
int slink, len;
int nxt[26];
};
struct PalindromicTree{
int cursuffix;
Node tree[N];
int tot;
void init(){
tree[0].len = -1, tree[0].slink = 0;
tree[1].len = 0, tree[1].slink = 0;
initNode(0);
initNode(1);
tot = 2, cursuffix = 1;
}
void initNode(int o){
memset(tree[o].nxt, -1, sizeof(tree[o].nxt));
}
void add(int pos){
int cur = cursuffix;
int idx = s[pos] - 'a';
while(true){
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]) break;
cur = tree[cur].slink;
}
if(tree[cur].nxt[idx] != -1){
cursuffix = tree[cur].nxt[idx];
return;
}
int nxt = tree[cur].nxt[idx] = tot++;
initNode(nxt);
tree[nxt].len = tree[cur].len + 2;
cursuffix = nxt;
if(tree[nxt].len == 1){
tree[nxt].slink = 1;
return;
}
while(true){
cur = tree[cur].slink;
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]){
tree[nxt].slink = tree[cur].nxt[idx];
break;
}
}
}
};
PalindromicTree pt;
int main(){
while(~scanf("%s", s)){
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
int n = strlen(s);
pt.init();
for(int i = 0; i < n; i++){
pt.add(i);
l[i] = max(l[i], pt.tree[pt.cursuffix].len);
}
pt.init();
reverse(s, s + n);
for(int i = 0; i < n; i++){
pt.add(i);
r[n - i - 1] = max(r[n - i - 1], pt.tree[pt.cursuffix].len);
}
int ans = 0;
for(int i = 0; i < n; i++){
if(i < n - 1) ans = max(ans, l[i] + r[i + 1]);
else ans = max(ans, l[i]);
}
printf("%d\n", ans);
}
}
拉拉队排练 - HYSBZ - 2160
思路
首先记录下每个状态节点被访问了几次,然后用拓扑排序的思想,将这一次数通过slink向下传递,因为长的回文字符串包括短的回文字符串,然后新开一个数组,以len为下标,记录下次数,最后倒回来扫这一数组加快速幂得到答案
这里并不需要真正的拓扑排序,考虑到字符串的后缀节点必然会比该字符串节点先建立,因此只需要将静态分配的顺序倒回来扫一遍即可
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
const int MOD = 19930726;
typedef long long ll;
char s[N];
ll nums[N];
ll quickPow(ll a, ll b){
ll ans = 1, base = a;
while(b){
if(b&1) ans = (ans * base) % MOD;
base = (base * base) % MOD;
b >>= 1;
}
return ans;
}
struct Node{
int slink, len;
ll cnt;
int nxt[26];
};
struct PalindromicTree{
int cursuffix;
Node tree[N];
int tot;
void init(){
tree[0].len = -1, tree[0].slink = 0, tree[0].cnt = 0;
tree[1].len = 0, tree[1].slink = 0, tree[0].cnt = 0;
initNode(0);
initNode(1);
tot = 2, cursuffix = 1;
}
void initNode(int o){
memset(tree[o].nxt, -1, sizeof(tree[o].nxt));
}
void add(int pos){
int cur = cursuffix;
int idx = s[pos] - 'a';
while(true){
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]) break;
cur = tree[cur].slink;
}
if(tree[cur].nxt[idx] != -1){
cursuffix = tree[cur].nxt[idx];
tree[cursuffix].cnt++;
return;
}
int nxt = tree[cur].nxt[idx] = tot++;
initNode(nxt);
tree[nxt].len = tree[cur].len + 2;
tree[nxt].cnt = 1;
cursuffix = nxt;
if(tree[nxt].len == 1){
tree[nxt].slink = 1;
return;
}
while(true){
cur = tree[cur].slink;
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]){
tree[nxt].slink = tree[cur].nxt[idx];
break;
}
}
}
void getCnt(){
for(int i = tot - 1; i >= 2; i--){
tree[tree[i].slink].cnt += tree[i].cnt;
}
}
};
PalindromicTree pt;
int main(){
int n;
ll k;
while(~scanf("%d%lld%s", &n, &k, s)){
pt.init();
for(int i = 0; s[i]; i++){
pt.add(i);
}
pt.getCnt();
ll ans = 1;
memset(nums, 0, sizeof(nums));
for(int i = 2; i < pt.tot; i++){
nums[pt.tree[i].len] += pt.tree[i].cnt;
}
bool flag = false;
for(int i = N - 2; i > 0; i -= 2){
ll tmp = min(nums[i], k);
k -= tmp;
ans = ans * quickPow(i, tmp) % MOD;
if(k == 0){
flag = true;
break;
}
}
if(flag) printf("%lld\n", ans);
else puts("-1");
}
}
Palisection - CodeForces - 17E
题意
求相交的回文字符串对数量
思路
正反跑两遍回文树,得到以i为左端点的字符串数量l[i],以及以i为右端点的字符串数量r[i],于是答案等于总回文字符串对 - 不相交的回文字符串对,其中不相交回文字符串对为 sum(l[i] * (g[i + 1] + +g[i + 2] + ... + g[n]))
本题莫名卡内存,需要把nxt数组设为vector,以时间换空间
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <iostream>
using namespace std;
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const int MOD = 51123987;
typedef long long ll;
char s[N];
vector<int> l;
struct Node{
int slink, len, cnt;
vector<pair<int, int> > nxt;
};
struct PalindromicTree{
int cursuffix;
vector<Node> tree;
int tot;
void init(){
tree.clear();
tree.resize(2);
tree[0].len = -1, tree[0].slink = 0;
tree[1].len = 0, tree[1].slink = 0;
tot = 2, cursuffix = 1;
}
int nxtFind(int u, int idx){
for(int i = 0; i < tree[u].nxt.size(); i++){
if(tree[u].nxt[i].first == idx) return i;
}
return -1;
}
void add(int pos){
int cur = cursuffix;
int idx = s[pos] - 'a';
while(true){
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]) break;
cur = tree[cur].slink;
}
int tmp = nxtFind(cur, idx);
if(tmp != -1){
cursuffix = tree[cur].nxt[tmp].second;
return;
}
int nxt = tot++;
tree[cur].nxt.push_back(make_pair(idx, nxt));
tree.emplace_back(Node());
tree[nxt].len = tree[cur].len + 2;
cursuffix = nxt;
if(tree[nxt].len == 1){
tree[nxt].slink = 1;
tree[nxt].cnt = 1;
return;
}
while(true){
cur = tree[cur].slink;
int curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]){
tree[nxt].slink = tree[cur].nxt[nxtFind(cur, idx)].second;
break;
}
}
tree[nxt].cnt = tree[tree[nxt].slink].cnt + 1;
}
};
PalindromicTree pt;
int main(){
int n;
while(~scanf("%d%s", &n, s)){
l.clear();
pt.init();
reverse(s, s + n);
for(int i = 0; i < n; i++){
pt.add(i);
l.push_back(pt.tree[pt.cursuffix].cnt);
if(i > 0) l[i] = (l[i] + l[i - 1]) % MOD;
}
reverse(l.begin(), l.end());
reverse(s, s + n);
ll ans = ((ll)l[0] * (l[0] - 1)) % MOD * 25561994LL % MOD;
pt.init();
for(int i = 0; i < n; i++){
pt.add(i);
if(i < n - 1) ans = ((ans - (ll)pt.tree[pt.cursuffix].cnt * l[i + 1]) % MOD + MOD) % MOD;
}
printf("%lld\n", ans);
}
}