概述
一个 广泛的 用于树的计数、生成的序列。
树 → prufer \rightarrow\text{prufer} →prufer 序列
百度百科:
一种生成 p r u f e r \rm{prufer} prufer 序列的方法是迭代删点,直到原图仅剩两个点。对于一棵顶点已经经过编号的树 T T T ,顶点的编号为 1 , 2 , … , n 1,2,\dots,n 1,2,…,n ,在第 x x x 步时,移去所有叶子节点(度为 1 1 1 的顶点)中标号最小的顶点和相连的边,并把与它相邻的点的编号加入 p r u f e r \rm{prufer} prufer 序列中,重复以上步骤直到原图仅剩两个顶点。
蒟蒻作者这样说:
举个栗子:这样的树。长得真丑
4--2--1--6
| |
5 3--7
- 第一次:移除 5 5 5 ,将 4 4 4 加入序列。
- 第二次:移除 4 4 4 ,将 2 2 2 加入序列。
- 第三次:移除 2 2 2 ,将 1 1 1 加入序列。
- 第四次:移除 6 6 6 ,将 1 1 1 加入序列。
- 第五次:移除 1 1 1 ,将 3 3 3 加入序列。
所以结果就是 ⟨ 4 , 2 , 1 , 1 , 3 ⟩ \langle 4,2,1,1,3\rangle ⟨4,2,1,1,3⟩ 。
性质
结论一: p r u f e r \rm{prufer} prufer 序列长度为 n − 2 n-2 n−2
结论二:一个点出现的次数恰为度数减一
构造的方法有另一种理解。选择一条边,这条边满足其中一个端点度数为 1 1 1 。将这条边的端点中,度数不为 1 1 1 的点加入 prufer \text{prufer} prufer 序列,而后删掉这条边。
在这种理解下,可以认为所有边都被删去了。最后一条边的删除没有加入点罢了。
那么一个度数为 d d d 的点,前 d − 1 d-1 d−1 条边删去的时候,都会将其加入 prufer \text{prufer} prufer 序列。最后一次则不会。
而且,这也可以用于证明结论一。
结论三: p r u f e r \rm{prufer} prufer 序列与树一一对应
prufer \text{prufer} prufer 序列 → \rightarrow → 树
假设 prufer \text{prufer} prufer 序列为 ⟨ a 1 , a 2 , a 3 , … , a n − 2 ⟩ \langle a_1,a_2,a_3,\dots,a_{n-2}\rangle ⟨a1,a2,a3,…,an−2⟩ 。
我们从前往后(即按照删去的顺序)考虑。
对于第一条被删掉的边, a 1 a_1 a1 一定是其中一个端点。谁是另一个端点呢?假设是 x x x 。
由于
x
x
x 的度数一定是
1
1
1 ,根据 结论二
,出现次数为
0
0
0 。根据定义,
x
x
x 是最小的。
所以: x x x 是未出现的、编号最小的一个!然后将 x x x 和 a 1 a_1 a1 连边。第一条边就搞定了。
a 1 a_1 a1 的度数明显会变小 1 1 1 ; a 1 a_1 a1 没有任何使用价值了。所以可以将 a 1 a_1 a1 扔掉——这等价于让 a 1 a_1 a1 的度数减小了 1 1 1 ,因为我们是根据 prufer \text{prufer} prufer 序列中出现的次数来推测度数的。
x x x 呢?以后的选择中一定没有 x x x 了(因为 x x x 的度数是 1 1 1 ,而且已经连了一条边)。以后都不考虑 x x x 。就当他不存在吧!
算法雏形已经有了。
百度百科:
设 ⟨ a 1 , a 2 , … , a n − 2 ⟩ \langle a_1,a_2,\dots,a_{n-2}\rangle ⟨a1,a2,…,an−2⟩ 为一棵有 n n n 个节点的树的 prufer \text{prufer} prufer 序列,另建一个集合 G = { 1 , 2 , 3 , … , n } \Bbb G=\{1,2,3,\dots,n\} G={1,2,3,…,n} ,找出 G \Bbb G G 中最小的未在 prufer \text{prufer} prufer 序列中出现过的数,将该点与 prufer \text{prufer} prufer 序列中首项连一条边,并将该点和 prufer \text{prufer} prufer 序列首项删除,重复操作 n − 2 n-2 n−2 次,将集合中剩余的两个点之间连边即可。
蒟蒻作者这样说:
代码大概长这样:
set<int> G; int cnt[N];
void pruferToTree(int a[],int n){
for(int i=n-2; i>=1; --i)
++ cnt[a[i]];
for(int i=1; i<=n; ++i)
if(not cnt[i])
G.insert(i);
for(int i=1; i<=n-2; ++i){
int t = *G.begin();
addEdge(t,a[i]); // 加边的函数
G.erase(t);
if(0 == (-- cnt[a[i]]))
G.insert(i);
}
int x = *G.begin();
G.erase(x);
addEdge(x,*G.begin());
G.clear(); // 清空,为下一次做准备
}
发现 prufer \text{prufer} prufer 序列可以 唯一 转化为一颗树。所以 prufer \text{prufer} prufer 序列与树是对应的。
实战
树的计数
题目描述
一个
n
n
n 个点的树,有多少种不同的形态?两种形态不同,当且仅当邻接矩阵不同。
解答
就是
prufer
\text{prufer}
prufer 序列。每一个数字都不大于
n
n
n ,共
n
−
2
n-2
n−2 个数字,共
n
n
−
2
n^{n-2}
nn−2 种情况。
树的计数贰
题目描述
有一个
n
n
n 个点的树。编号为
i
i
i 的点,度数为
d
i
d_i
di 。不同形态的树有多少种?
解答
还是
prufer
\text{prufer}
prufer 序列。由于
i
i
i 在
prufer
\text{prufer}
prufer序列中恰好出现了
d
i
−
1
d_i-1
di−1 次,共
n
−
2
n-2
n−2 个数,根据排列组合相关定理,总方案数为
(
n
−
2
)
!
∏
i
=
1
n
(
d
i
−
1
)
!
\frac{(n-2)!}{\prod_{i=1}^n (d_i-1)!}
∏i=1n(di−1)!(n−2)! 。
树的计数叁
题目描述
n
n
n 个点的有根树,不同形态的数量?
解答
直接定一个根即可。答案
n
n
−
2
×
n
=
n
n
−
1
n^{n-2}\times n=n^{n-1}
nn−2×n=nn−1 。
城市地铁规划
传送门,这是一道 prufer \text{prufer} prufer 序列和 d p dp dp 结合食用的神奇题目。