【知识】Prüfer 编码
Prüfer 序列
Prufer 序列可以将一个带标号
Prufer 序列可以方便地解决一类树相关的计数问题,比如 凯莱定理:
对树构造 Prüfer 序列
Prufer 是这样构造的:
每次选择一个编号最小的叶节点并删掉它,然后在序列中记录下它连接到的那个节点。
重复
-
显然,使用堆可以做到 的复杂度。 -
使用一个指针代替堆找最小值,可以做到 的复杂度。
具体而言,指针指向编号最小的叶节点。每次删掉它之后,如果产生了新的叶节点且编号比指针指向的更小,则直接继续删掉,否则自增找到下一个编号最小的叶节点。
Prufer 序列的性质
从上述构造 Prüfer 序列的过程可以看出 Prüfer 序列具有以下两个性质:
- 在构造完 Prüfer 序列后,原树中会剩下两个节点,其中一个一定是编号最大的点
。 - 每个节点在序列中出现的次数是其度数减
,因此没有出现的就是叶节点。
用 Prufer 序列构造树
根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。
每次我们选择一个编号最小的度数为
-
同样地,显然,使用堆可以做到 的复杂度。 -
类似地,使用一个指针代替堆找最小值,可以做到 的复杂度。
具体而言,指针指向编号最小的度数为
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int f[N], d[N], p[N];
void tree2prufer()
{
for (int i = 1; i < n; i ++ )
{
scanf("%d", &f[i]);
d[f[i]] ++ ;
}
for (int i = 0, j = 1; i < n - 2; j ++ )
{
while (d[j]) j ++ ;
p[i ++ ] = f[j];
while (i < n - 2 && -- d[p[i - 1]] == 0 && p[i - 1] < j) p[i ++ ] = f[p[i - 1]];
}
for (int i = 0; i < n - 2; i ++ ) printf("%d ", p[i]);
}
void prufer2tree()
{
for (int i = 1; i <= n - 2; i ++ )
{
scanf("%d", &p[i]);
d[p[i]] ++ ;
}
p[n - 1] = n;
for (int i = 1, j = 1; i < n; i ++, j ++ )
{
while (d[j]) j ++ ;
f[j] = p[i];
while (i < n - 1 && -- d[p[i]] == 0 && p[i] < j) f[p[i]] = p[i + 1], i ++ ;
}
for (int i = 1; i <= n - 1; i ++ ) printf("%d ", f[i]);
}
int main()
{
scanf("%d%d", &n, &m);
if (m == 1) tree2prufer();
else prufer2tree();
return 0;
}
Cayley 公式 (Cayley's formula)
完全图
怎么证明?方法很多,但是用 Prüfer 序列证是很简单的。任意一个长度为
图连通方案数
Prüfer 序列可能比你想得还强大。它能创造比 凯莱公式 更通用的公式。比如以下问题:
一个
个点 条边的带标号无向图有 个连通块。我们希望添加 条边使得整个图连通。求方案数。
证明
设
对于第
现在我们要枚举
好的这是一个非常不喜闻乐见的式子。但是别慌!我们有多元二项式定理:
那么我们对原式做一下换元,设
化简得到
即
证毕!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!