Prufer 序列
一个长度为 ,值域为 的序列可以表示一棵 个节点的有标号无根树,这个序列就是 Prufer 序列。
对树构造 Prufer 序列
每次选择一个编号最小的叶节点并删掉它,然后在序列末尾加入它连接到的那个节点的编号。
重复 次后就只剩下两个节点,算法结束。
Prufer 序列的性质
从上述构造 Prufer 序列的过程可以看出 Prufer 序列具有一个重要的性质:每个节点在序列中出现的次数是其度数减 。
用 Prufer 序列构造树
根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。
枚举 Prufer 序列的每一项,每次我们选择一个编号最小的度数为 的节点,与当前枚举到的点连接,然后将这两个点的度数减 。重复 次后就只剩下两个度数为 的节点,其中一个是 ,把它们连接起来即可。
模板题代码:
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
typedef long long ll;
using namespace std;
const int N = 5000001;
int n, _, fa[N], pf[N], d[N];
ll t2p(){
for (int i = 1;i < n;i ++) scanf("%d", &fa[i]), d[fa[i]] ++;
int p = 0, c = 0;ll res = 0;
while (c < n - 2){p ++;
while (d[p]) p ++;pf[++ c] = fa[p];
while (c < n - 2 && !-- d[pf[c]] && pf[c] < p) pf[c + 1] = fa[pf[c]], c ++;
}
for (int i = 1;i <= n - 2;i ++) res ^= 1ll * i * pf[i];
return res;
}
ll p2t(){
for (int i = 1;i <= n - 2;i ++) scanf("%d", &pf[i]), d[pf[i]] ++;pf[n - 1] = n;
int p = 0, c = 0;ll res = 0;
while (c < n - 1){p ++;
while (d[p]) p ++;fa[p] = pf[++ c];
while (c < n - 1 && !-- d[pf[c]] && pf[c] < p) fa[pf[c]] = pf[c + 1], c ++;
}
for (int i = 1;i < n;i ++) res ^= 1ll * i * fa[i];
return res;
}
int main(){
scanf("%d%d", &n, &_);
printf("%lld", _ == 1 ? t2p() : p2t());
}
Cayley 公式
我们发现两种转换方式都是唯一的,于是可以知道 个节点的有标号无根树与长度为 值域为 的数列构成双射,于是,有 棵不同的 个节点的有标号无根树。
另外,我们给无根树定个根,一共有 种可能的根,那么就有 棵不同的 个节点的有标号无根树。
[HNOI2004] 树的计数
看到关于度数的计数,考虑 Prufer 序列。
我们知道,一个节点在 Prufer 序列里出现的次数为它的度数 ,那么 出现的次数是 , 出现的次数是 ,……, 出现的次数是 ,这样的序列一共有 个。
Clues
首先把已有的边连起来,然后问题就转化成了:共有 个点,已有的边已经连出了 个连通块,第 个连通块的大小为 ,求用 条边将它们连接起来的方案数。
设第 个联通块的点在 Prufer 序列里出现了 次,答案为:
发现后者实质上就是 Prufer 序列的总数,也就是 ,方案数也就是 ,这也是一个经典结论。
Figures
考虑枚举 Prufer 序列,设第 个联通块的点在枚举的 Prufer 序列里出现了 次,那么答案为 。设 为 ,那么答案就为 。
考虑组合意义, 也就是在第 个连通块中顺序选出不重复的 个点,再在第 个连通块中顺序选出不重复的 个点,……,再在第 个连通块中顺序选出不重复的 个点。那么对于所有的 Prufer 序列, 就相当于在整个图中按顺序选出不重复的 个点,设 为 ,则方案数为 。