Prüfer 序列
创建时间:2025-08-10
Prüfer 序列的概念与构造 Prüfer 序列
有时树或序列的问题很难直接处理,想到将一棵树转化为一个序列或将序列转化为树。转化的方式有很多种,最常见的有 dfs 序、欧拉序,Prüfer 序列也是其中的一种。与前两者不同的是,前两种方式的序列与树之间的映射不是唯一的,而 Prüfer 序列恰好满足一棵树只有一个 Prüfer 序列,一个 Prüfer 序列只对应一棵树。
记 \(fa_i\) 为树中 \(i\) 的父节点编号,\(deg_i\) 为树中 \(i\) 的度数,\(prufer_{1 \sim n-2}\) 为 Prüfer 序列。
得到 Prüfer 序列需要不断进行以下操作:
- 找到度数为 \(1\) 的节点,即叶子节点中编号最小的节点,记为 \(leaf\)。
- 对于第 \(i\) 次操作得到的 \(leaf\),令 \(prufer_i=fa_{leaf}\)。
- 删掉 \(leaf\),即 \(deg_{fa_{leaf}}:=deg_{fa_{leaf}}-1\)。
显然,在 \(n-2\) 次操作后剩下了联通的两个节点,其中一定有一个是树的根。
这么做得到的序列是唯一的。
按逻辑写出代码:
void get_prufer() {
int cur, leaf;
for (int i = 1; i <= n; i++)
if (deg[i] == 1) {
cur = leaf = i;
break;
}
for (int i = 1; i <= n - 2; i++) {
prufer[i] = fa[leaf];
deg[fa[leaf]]--;
if (deg[fa[leaf]] == 1 && fa[leaf] < cur) {
leaf = fa[leaf];
continue;
}
cur++;
while (deg[cur] != 1)
cur++;
leaf = cur;
}
}
因为 \(cur\) 是单调不减的,\(leaf\) 也最多不超过 \(n\) 次,故该做法是线性的。
Prüfer 序列的性质及还原树结构
Prüfer 序列有以下几个常用性质:
1:构造 Prüfer 序列剩余的两个节点中必然有一个编号为 \(n\)。
假设剩余节点中没有 \(n\),则 \(n\) 一定在先前的操作中被删了。而当且仅当 \(n\) 的度数为 \(1\) 且不存在其他度数为 \(1\) 的节点时 \(n\) 会被删掉(因为 \(n\) 的编号比其他任何节点都大)。感性的理解会发现,在任何时刻,一颗树中总是有至少两个度数为 \(1\) 的节点。这与之前的推导矛盾,故假设不成立,\(n\) 自然会是剩余节点中的一个。
2:\(i\) 在 Prüfer 序列中的出现次数恰好为 \(deg_i-1\)。
因为当且仅当每当孩子节点被操作时,\(i\) 被添加到 Prüfer 序列中,所以 \(i\) 在 Prüfer 序列中出现次数就等于 \(i\) 的被操作过的孩子个数。当 \(i\) 为根时,\(i\) 一定是剩余节点中的一个,而另一个一定是 \(i\) 的孩子,所有与 \(i\) 相连的点都是 \(i\) 的孩子,故 \(i\) 的被操作过的孩子个数等于 \(deg_i-1\);当 \(i\) 不为根时,与 \(i\) 相连的节点中除了 \(i\) 的父节点都是 \(i\) 的孩子,且 \(i\) 的所有孩子都被操作过,所以 \(i\) 的被操作过的孩子个数也是 \(deg_i-1\)。综上知 \(i\) 在 Prüfer 序列中出次数一定为 \(deg_i-1\)。
有了这两个结论便能由 Prüfer 序列倒推得到原树:
void get_tree() {
fill(deg + 1, deg + n + 1, 1);
for (int i = 1; i <= n - 2; i++)
deg[prufer[i]]++; // 性质1
int cur, leaf;
for (int i = 1; i <= n; i++)
if (deg[i] == 1) {
cur = leaf = i;
break;
}
for (int i = 1; i <= n - 2; i++) {
fa[leaf] = prufer[i];
deg[fa[leaf]]--;
if (deg[fa[leaf]] == 1 && fa[leaf] < cur) {
leaf = fa[leaf];
continue;
}
cur++;
while (deg[cur] != 1)
cur++;
leaf = cur;
}
fa[leaf] = n; // 性质2(fa[n]=leaf等价)
}
Cayley 公式和广义 Cayley 公式
Cayley 公式:对于由 \(n\) 个节点构成的无根树的结构有 \(n^{n-2}\) 种
因为任意一个 Prüfer 序列显然都是可以且只对应一颗树的,而 Prüfer 序列的第 \(i\) 项一共有 \(n\) 种可能,一共 \(n-2\) 项。故总可能数为 \(n^{n-2}\)。
广义 Cayley 公式:对于 \(n\) 个节点,其中有 \(t\) 个特殊点,构成 \(t\) 棵树使任意两个特殊点不在同一颗树中,有 \(t n^{n-t-1}\) 种方案(\(t<n\))。
不妨改变节点编号使 \(t\) 个关键点编号分别为 \(n,n-1,n-2,\cdots,n-t+2,n-t+1\)。考虑将每个关键点视为其所在的树的根,虚拟节点 \(n+1\),将所有关键节点作为 \(n+1\) 的孩子,这样便构成了一棵包含 \(n+1\) 个节点的树。很显然的是,这棵新树的 Prüfer 序列有 \(n-1\) 项的最后 \(t-1\) 项均为 \(n+1\)。故新树的 Prüfer 有 \(n^{n-t-1}\) 种可能。而在建 Prüfer 序列后会剩下两个节点,这两个节点中一个一定为 \(n+1\),一个一定为某个关键点,这个关键点有 \(t\) 种可能,想由新树映射会原森林就必须直到剩下的节点。综上得总方案数 \(tn^{n-t-1}\)。
Prüfer 序列的其他常用结论与例题
1:\(n\) 个节点的有根树的结构有 \(n^{n-1}\) 种。
这是显然的,因为有根树个数等价于任意一颗无根树中选一个根的的方案数,无根树个数为 \(n^{n-2}\),选根有 \(n\) 中选择,故总数为 \(n^{n-2}\cdot n=n^{n-1}\)。
2:\(\sum_{i=1}^n (deg_i-1)=n-2\)。
由性质2知 \(i\) 在 Prüfer 序列中出现了 \(deg_i-1\) 次,而 Prüfer 序列的长度为 \(n-2\),故上式显然成立。
3:若已知 \(deg\) 数组,则满足条件的无根树个数为
因为无根树个数等价于 Prüfer 序列的个数,又由性质2知个数即为 \(deg_1-1\) 个 \(1\),\(deg_2-1\) 个 \(2\),\(deg_3-3\),\(\cdots\),\(deg_n-1\) 个 \(n\) 的排列总数,即 \(\frac {((deg_1-1)+(deg_2-1)+(deg_3-1)+\cdots+(deg_n-1))!} {(deg_1-1)!(deg_2-1)!(deg_3-1)\cdots(deg_n-1)!}=\frac {n-2}{\Pi_{i=1}^n(deg_i-1)!}\)。
应用:
P6086 【模板】Prüfer 序列(板子题);
P2624 [HNOI2008] 明明的烦恼(Prüfer 序列+组合+高精度);
CF156D Clues(Prüfer 序列+多元二项式定理);
P5454 [THUPC 2018] 城市地铁规划(Prüfer 序列+完全背包);
CF1109D Sasha and Interesting Fact from Graph Theory(广义 Cayley 公式)

浙公网安备 33010602011771号