回文自动机
概念讲解
什么是回文自动机?
回文自动机(PAM,Eertree)是一种用于处理回文串的数据结构。
核心思想
将所有回文串组织成树形结构。
实现
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960const int N = 100005;
const int ALPHABET = 26;
struct PAM {
int trie[N][ALPHABET];
int fail[N];
int len[N];
int count[N];
int last;
int idx;
char s[N];
int n;
PAM() {
init();
}
void init() {
memset(trie[0], 0, sizeof(trie[0]));
memset(trie[1], 0, sizeof(trie[1]));
len[0] = -1;
len[1] = 0;
fail[0] = 1;
fail[1] = 0;
last = 1;
idx = 1;
n = 0;
s[0] = -1;
}
int getFail(int x) {
while (s[n - len[x] - 1] != s[n]) {
x = fail[x];
}
return x;
}
void extend(char c) {
s[++n] = c;
int u = c - 'a';
int cur = getFail(last);
if (!trie[cur][u]) {
int v = ++idx;
memset(trie[v], 0, sizeof(trie[v]));
len[v] = len[cur] + 2;
fail[v] = trie[getFail(fail[cur])][u];
trie[cur][u] = v;
}
last = trie[cur][u];
count[last]++;
}
void build() {
for (int i = idx; i >= 2; i--) {
count[fail[i]] += count[i];
}
}
};应用场景
1. 本质不同回文串计数
1234567int countDistinctPalindromes(string s) {
PAM pam;
for (char c : s) {
pam.extend(c);
}
return pam.idx - 1; // 减去两个根节点
}2. 回文串出现次数
1// count[i] 表示以 i 结尾的回文串出现次数考点分析
初赛考查方式
- 选择题: 回文自动机的概念
- 程序阅读: 理解回文自动机
- 编程题: 回文自动机应用
常见题型
- 本质不同回文串计数
- 回文串出现次数
例题精讲
例题 1:本质不同回文串计数
题目: 求字符串中本质不同的回文子串个数。
解答:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172#include <iostream>
#include <cstring>
using namespace std;
const int N = 100005;
const int ALPHABET = 26;
struct PAM {
int trie[N][ALPHABET];
int fail[N];
int len[N];
int count[N];
int last;
int idx;
char s[N];
int n;
PAM() {
init();
}
void init() {
memset(trie[0], 0, sizeof(trie[0]));
memset(trie[1], 0, sizeof(trie[1]));
len[0] = -1;
len[1] = 0;
fail[0] = 1;
fail[1] = 0;
last = 1;
idx = 1;
n = 0;
s[0] = -1;
}
int getFail(int x) {
while (s[n - len[x] - 1] != s[n]) {
x = fail[x];
}
return x;
}
void extend(char c) {
s[++n] = c;
int u = c - 'a';
int cur = getFail(last);
if (!trie[cur][u]) {
int v = ++idx;
memset(trie[v], 0, sizeof(trie[v]));
len[v] = len[cur] + 2;
fail[v] = trie[getFail(fail[cur])][u];
trie[cur][u] = v;
}
last = trie[cur][u];
count[last]++;
}
};
int main() {
string s;
cin >> s;
PAM pam;
for (char c : s) {
pam.extend(c);
}
cout << pam.idx - 1 << endl;
return 0;
}练习题
- 洛谷 P5496 【模板】回文自动机(PAM)
查看答案
思路:回文自动机模板题。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include <iostream>
#include <cstring>
using namespace std;
const int N = 500005;
const int ALPHABET = 26;
struct PAM {
int trie[N][ALPHABET];
int fail[N];
int len[N];
int count[N];
int last;
int idx;
char s[N];
int n;
PAM() {
init();
}
void init() {
memset(trie[0], 0, sizeof(trie[0]));
memset(trie[1], 0, sizeof(trie[1]));
len[0] = -1;
len[1] = 0;
fail[0] = 1;
fail[1] = 0;
last = 1;
idx = 1;
n = 0;
s[0] = -1;
}
int getFail(int x) {
while (s[n - len[x] - 1] != s[n]) {
x = fail[x];
}
return x;
}
void extend(char c) {
s[++n] = c;
int u = c - 'a';
int cur = getFail(last);
if (!trie[cur][u]) {
int v = ++idx;
memset(trie[v], 0, sizeof(trie[v]));
len[v] = len[cur] + 2;
fail[v] = trie[getFail(fail[cur])][u];
trie[cur][u] = v;
}
last = trie[cur][u];
count[last]++;
}
void build() {
for (int i = idx; i >= 2; i--) {
count[fail[i]] += count[i];
}
}
};
int main() {
string s;
cin >> s;
PAM pam;
int last = 0;
for (char c : s) {
c = (c - 'a' + last) % 26 + 'a';
pam.extend(c);
last = pam.idx - 1;
cout << last << " ";
}
cout << endl;
return 0;
}- 洛谷 P3649 [APIO2014] 回文串
查看答案
思路:回文自动机,统计每个回文串的出现次数。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283#include <iostream>
#include <cstring>
using namespace std;
const int N = 300005;
const int ALPHABET = 26;
struct PAM {
int trie[N][ALPHABET];
int fail[N];
int len[N];
int count[N];
int last;
int idx;
char s[N];
int n;
PAM() {
init();
}
void init() {
memset(trie[0], 0, sizeof(trie[0]));
memset(trie[1], 0, sizeof(trie[1]));
len[0] = -1;
len[1] = 0;
fail[0] = 1;
fail[1] = 0;
last = 1;
idx = 1;
n = 0;
s[0] = -1;
}
int getFail(int x) {
while (s[n - len[x] - 1] != s[n]) {
x = fail[x];
}
return x;
}
void extend(char c) {
s[++n] = c;
int u = c - 'a';
int cur = getFail(last);
if (!trie[cur][u]) {
int v = ++idx;
memset(trie[v], 0, sizeof(trie[v]));
len[v] = len[cur] + 2;
fail[v] = trie[getFail(fail[cur])][u];
trie[cur][u] = v;
}
last = trie[cur][u];
count[last]++;
}
void build() {
for (int i = idx; i >= 2; i--) {
count[fail[i]] += count[i];
}
}
};
int main() {
string s;
cin >> s;
PAM pam;
for (char c : s) {
pam.extend(c);
}
pam.build();
long long ans = 0;
for (int i = 2; i <= pam.idx; i++) {
ans = max(ans, (long long)pam.len[i] * pam.count[i]);
}
cout << ans << endl;
return 0;
}- 洛谷 P4287 [SHOI2011] 双倍回文
查看答案
思路:回文自动机,找双倍回文串。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889#include <iostream>
#include <cstring>
using namespace std;
const int N = 500005;
const int ALPHABET = 26;
struct PAM {
int trie[N][ALPHABET];
int fail[N];
int len[N];
int count[N];
int last;
int idx;
char s[N];
int n;
PAM() {
init();
}
void init() {
memset(trie[0], 0, sizeof(trie[0]));
memset(trie[1], 0, sizeof(trie[1]));
len[0] = -1;
len[1] = 0;
fail[0] = 1;
fail[1] = 0;
last = 1;
idx = 1;
n = 0;
s[0] = -1;
}
int getFail(int x) {
while (s[n - len[x] - 1] != s[n]) {
x = fail[x];
}
return x;
}
void extend(char c) {
s[++n] = c;
int u = c - 'a';
int cur = getFail(last);
if (!trie[cur][u]) {
int v = ++idx;
memset(trie[v], 0, sizeof(trie[v]));
len[v] = len[cur] + 2;
fail[v] = trie[getFail(fail[cur])][u];
trie[cur][u] = v;
}
last = trie[cur][u];
count[last]++;
}
};
int main() {
int n;
cin >> n;
string s;
cin >> s;
PAM pam;
for (char c : s) {
pam.extend(c);
}
int ans = 0;
for (int i = 2; i <= pam.idx; i++) {
if (pam.len[i] % 4 == 0) {
int half = pam.len[i] / 2;
int p = pam.fail[i];
while (p > 1) {
if (pam.len[p] == half) {
ans = max(ans, pam.len[i]);
break;
}
p = pam.fail[p];
}
}
}
cout << ans << endl;
return 0;
}易错点
⚠️ 注意:
- 回文自动机有两个根节点
- fail 指针的构建是关键
- 回文自动机是字符串算法的高级内容
- 注意数组大小