回文树
创建时间:2026-05-06
回文树的原理
回文树(Palindromic Tree),又称回文自动机(Palindromic Automaton,PAM),能够高效处理回文子串问题。
回文树的每个节点对应原串一个本质不同的回文子串,称该回文子串为节点 \(x\) 的串。每个节点需要维护以下信息:
- \(len_x\):节点 \(x\) 的串的长度。
- \(fail_x\):节点 \(x\) 的串的最长回文后缀对应的节点编号(与 AC 自动机的 \(fail\),后缀自动机的 \(link\) 类似)。
- \(ch_{x, c}\):在节点 \(x\) 的串的两端添加字符 \(c\) 后对应节点的编号。
回文树有两个根。根据串长分类,偶根 \(rt_0\) 为所有偶回文子串的根,\(rt_1\) 为所有奇回文子串的根。
为了方便,我们令 \(len_{rt_0} = 0, len_{rt_1} = -1\),并初始化每个节点的 \(fail_x\) 为 \(rt_1\),\(ch_{x, c}\) 为 \(rt_0\)。
记 \(n=|s|\),约定 \(st\) 表示字符串 \(s, t\) 按顺序拼接,\(pre(s, i)\) 表示 \(s_1 s_2 \cdots s_i\),\(suf(s, i)\) 表示 \(s_i s_{i+1} \cdots s_n\)。
我们采用增量法对字符串 \(s\) 构建回文树。设当前加入第 \(i\) 个字符 \(c = s_i\),有 \(pre(s, i-1)\) 的回文后缀 \(t\)。那么,以 \(s_i\) 结尾的回文子串一定形如 \(ctc\)。
设 \(s_1 s_2 \cdots s_{i-1}\) 的最长回文后缀对应节点为 \(lst\)。从 \(lst\) 开始跳 \(fail\) 并找到第一个满足 \(s_{n-len_p-1} = c\) 的节点 \(p\),其对应的串就是最长的 \(t\)。
如果 \(ctc\) 已经出现过了,即 \(ch_{p, c} \neq rt_0\),为保证回文树每个节点代表一个互不相同的回文子串,此时不应再新增节点。
否则,新建节点 \(cur\) 代表串 \(ctc\),显然,\(len_{cur} = len_p + 2\)。类似的,\(fail_{cur}\) 可以从 \(fail_p\) 开始跳 \(fail\) 找到第一个满足 \(s_{n-len_q-1}=c\) 的 \(q\),令 \(fail_{cur} = ch_{q, c}\)。
最后更新 \(lst \leftarrow ch_{p, c}\) 就完成了。
暴力跳 \(fail\) 的总复杂度看起来是 \(O(n^2)\) 的。事实上,每次跳 \(fail\) 都导致 \(len\) 至少减 \(1\),应用势能分析易知每次增量的复杂度均摊 \(O(1)\),总复杂度 \(O(n)\)。
模板代码:
template<int chars>
struct PAM_Node {
int len, fail, ch[chars];
PAM_Node(int l) : len(l), fail(1) {
memset(ch, 0, sizeof(ch));
}
};
template<int chars>
class PAM {
private:
vector<PAM_Node<chars>> t;
vector<int> s;
public:
int lst;
PAM() : lst(1) {
t.emplace_back(0);
t.emplace_back(-1);
}
int size() {
return t.size();
}
PAM_Node<chars>& operator [] (int i) {
return t[i];
}
void extend(int c) {
auto get_fail = [&](int x) {
int n = s.size();
while (t[x].len + 2 > n || c != s[n - t[x].len - 2]) {
x = t[x].fail;
}
return x;
};
s.push_back(c);
int p = get_fail(lst);
if (!t[p].ch[c]) {
t.emplace_back(t[p].len + 2);
int cur = t.size() - 1;
t[cur].fail = t[get_fail(t[p].fail)].ch[c];
t[p].ch[c] = cur;
}
lst = t[p].ch[c];
}
};
回文树的应用
(1)回文树可以 \(O(n)\) 求出字符串 \(s\) 的每个前缀的最长回文后缀/回文后缀数量。
每个前缀的最长回文后缀就是 \(lst\) 的串。回文后缀数量是每次增量时 \(size\) 的差,也可以直接维护一个 \(num_x\) 表示 \(x\) 的回文后缀数,通过 \(num_x = num_{fail_x}+1\) 转移。
例题:
P5496 【模板】回文树 / 回文自动机(PAM)
P4555 [国家集训队] 最长双回文串
CF17E Palisection
(2)回文树可以在求 \(fail\) 的同时维护 \(x\) 的 \(fail\) 祖先中第一个 \(y\) 满足 \(len_y \le \frac 1 2 len_x\) 等信息。
设 \(trans_i\) 为 \(i\) 的 \(fail\) 祖先中第一个 \(len \le \frac 1 2 len_x\) 的节点。类比求 \(fail\),从 \(trans_p\) 出发暴力跳 \(fail\) 直到找到第一个 \(c = s_{n-len_x-1}\) 的节点 \(q\),故 \(trans_{cur} = ch_{q, c}\)。
例题:
P4287 [SHOI2011] 双倍回文
P4762 [CERC2014] Virus synthesis
(3)回文树可以 \(O(n)\) 找出字符串 \(s\) 的所有本质不同回文字串与其出现次数。
回文树的每个节点都对应一个本质不同回文子串。设 \(cnt_x\) 为 \(x\) 的串的出现次数。每次插入字符后应将 \(lst\) 和 \(lst\) 的所有 \(fail\) 祖先的 \(cnt_x\) 加 \(1\)(与 AC 自动机类似)。
例题:
P3649 [APIO2014] 回文串
P5555 秩序魔咒
P5685 [JSOI2013] 快乐的 JYY
(4)回文树不支持低于 \(O(n)\) 的合并,但很多其它操作可以做到均摊 \(O(1)\),如双端插入、后插前删。
双端插入:
朴素回文树只支持在字符串 \(s\) 的末尾加入字符,这是因为回文树增量插入都基于 \(lst\) 变量,即最长回文后缀对应节点。我们可以维护变量 \(front\) 和 \(back\),分别表示最长回文前缀对应节点和最长回文后缀对应节点,这样就能实现双端插入回文树。
后插前删:
简单来讲,就是维护一个队列。我们在朴素回文树的基础上维护每个字串最后一次出现位置,但暴力上传标记太慢了。注意到 \(fail\) 树中只有叶子节点才能删除,所以额外记录每个节点的儿子个数,加上一些乱搞就做完了。
例题:
P8406 [COCI 2021/2022 #6] Palindromi
CF1738H Palindrome Addicts
(5)回文树擅长优化回文子串划分的 DP 问题,通常可以做到 \(O(n \log n)\)。
以回文串划分计数为例。
很容易设计状态 \(f_i\) 表示 \(pre(s, i)\) 的划分方案数,则 \(f_i = \sum\limits_x f_{i-len_x}\),\(x\) 是 \(pre(s, i)\) 的回文后缀对应的节点。答案为 \(f_n\)。直接做是 \(O(n^2)\) 的。
\(pre(s, i)\) 的回文后缀等价于 \(pre(s, i)\) 的最长回文后缀的回文后缀。根据回文的对称性,一个回文串的 period / border 和它的回文后缀一一对应。又因为一个串的 border 长度一定可以划分为 \(O(\log n)\) 个等差数列,所以 \(pre(s, i)\) 的回文后缀长度可以划分为 \(O(\log n)\) 个等差数列。
在回文树中维护 \(diff_x = len_x - len_{fail_x}\),\(slink_x\) 表示 \(x\) 的 \(fail\) 祖先中第一个满足 \(diff\) 不等于 \(diff_x\) 的节点,或者说,\(slink_x\) 表示 \(x\) 所在的等差数列的首项。设 \(g_x = \sum\limits_{diff_y = diff_x} f_{i-len_y}\),动态维护 \(g\) 和 \(f\) 即可,像这样:
for (int x = pam.lst; x; x = pam[x].slink) {
g[x] = f[i - (pam[pam[x].slink].len + pam[x].diff)];
if (pam[x].diff == pam[pam[x].fail].diff) {
g[x] = (g[x] + g[pam[x].fail]) % mod;
}
f[i] = (f[i] + g[x]) % mod;
}
例题:
P14185 回文子串划分计数
P14771 [ICPC 2024 Seoul R] Palindromic Length
CF932G Palindrome Partition

浙公网安备 33010602011771号