回文自动机 (Palindromic Automaton) 模板详解

回文自动机 (Palindromic Automaton) 模板详解

1. 简介

回文自动机(PAM,也称回文树)是一种用于处理字符串中回文子串的高效数据结构。它可以在线性时间内维护字符串中的所有回文子串信息。

主要特点:

  • 线性时间复杂度
  • 在线处理字符串
  • 维护所有回文子串
  • 支持动态添加字符

2. 实现原理

2.1 基本概念

  1. 节点结构

    • 表示一个回文子串
    • 包含长度、后缀链接等信息
  2. 后缀链接

    • 指向最长的回文后缀
    • 用于快速转移状态

2.2 核心策略

  1. 状态转移

    • 利用后缀链接找到匹配位置
    • 维护回文子串的计数
  2. 节点维护

    • 两个初始节点(空串和长度为-1的辅助节点)
    • 动态添加新的回文节点

3. 模板代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
struct PAM {
static constexpr int ALPHABET_SIZE = 26;
struct Node {
int len;
int link;
int cnt;
std::array<int, ALPHABET_SIZE> next;
Node() : len{}, link{}, cnt{}, next{} {}
};
std::vector<Node> t;
int suff;
std::string s;
PAM() {
init();
}
void init() {
t.assign(2, Node());
t[0].len = -1;
suff = 1;
s.clear();
}
int newNode() {
t.emplace_back();
return t.size() - 1;
}

bool add(char c, char offset = 'a') {
int pos = s.size();
s += c;
int let = c - offset;
int cur = suff, curlen = 0;

while (true) {
curlen = t[cur].len;
if (pos - 1 - curlen >= 0 && s[pos - 1 - curlen] == s[pos]) {
break;
}
cur = t[cur].link;
}
if (t[cur].next[let]) {
suff = t[cur].next[let];
return false;
}

int num = newNode();
suff = num;
t[num].len = t[cur].len + 2;
t[cur].next[let] = num;

if (t[num].len == 1) {
t[num].link = 1;
t[num].cnt = 1;
return true;
}

while (true) {
cur = t[cur].link;
curlen = t[cur].len;
if (pos - 1 - curlen >= 0 && s[pos - 1 - curlen] == s[pos]) {
t[num].link = t[cur].next[let];
break;
}
}

t[num].cnt = 1 + t[t[num].link].cnt;

return true;
}
};

4. 函数说明

4.1 结构体说明

1
2
3
4
5
6
struct Node {
int len; // 回文串长度
int link; // 后缀链接
int cnt; // 出现次数
std::array<int, ALPHABET_SIZE> next; // 转移数组
};

4.2 主要函数

  • init(): 初始化自动机
  • newNode(): 创建新节点
  • add(): 添加字符并更新自动机

5. 时间复杂度分析

  • 初始化:O(1)
  • 添加字符:均摊 O(1)
  • 空间复杂度:O(n),n 为字符串长度

6. 应用场景

  1. 统计不同回文子串个数
  2. 计算回文子串出现次数
  3. 动态维护回文信息
  4. 回文串相关问题求解

7. 使用示例

7.1 统计不同回文子串个数

1
2
3
4
5
6
7
8
PAM pam;
string s = "ababa";
int count = 0;
for (char c : s) {
if (pam.add(c)) {
count++; // 每当添加新节点时计数
}
}

7.2 计算回文子串出现次数

1
2
3
4
5
6
7
8
9
PAM pam;
string s = "aba";
for (char c : s) {
pam.add(c);
}
int total = 0;
for (int i = 2; i < pam.t.size(); i++) {
total += pam.t[i].cnt; // 累加每个节点的计数
}

8. 注意事项

  1. 初始化

    • 需要两个特殊节点
    • 正确设置初始状态
  2. 字符集处理

    • 默认使用小写字母
    • 可以通过 offset 参数调整
  3. 内存管理

    • 注意节点数量上限
    • 合理设置数组大小

9. 总结

回文自动机是一个强大的字符串处理工具,它可以高效地维护和查询字符串中的回文信息。通过巧妙的后缀链接设计,实现了线性时间的处理效率。该数据结构在处理回文相关问题时具有独特的优势,是字符串算法中的重要工具。