【算法】Prüfer编码 —— HNOI2004树的计数
的确,如果不知道这个编码的话的确是一脸懵逼。在这里放一篇认为讲的很详细的 BLOG,有关于编码的方式 & 扩展在里面都有所提及。
欢迎点此进入 --> 大佬的博客
在这里主要想推导一下最后面的扩展公式是怎么来的。问题:给定一棵树 & 树上各个节点的度数,求有多少棵满足要求的生成树?
在了解了Prüfer编码之后,我们已经知道编码与生成树是一一对应的关系了,且一个数在Prüfer编号上面出现的次数即为它的度数 - 1;问题转化成为:一个长度为 \(n - 2\) 的序列中均为范围在 \(1\) ~ \(n\)的数字,规定了每个数字出现的次数,问有多少个合法的序列?首先不考虑是否合法,规定排列当中的数字各不相同,这样的排列有 \(\left ( n - 2 \right )!\) 种。但这样明显统计多了,因为当有相同的数字出现时,交换它们之间的相对位置并不会改变排列的实质。于是我们要在此基础之上除以每一种相同数字的排列数 : \(\prod \left ( d[i] - 1 \right )!\)。
所以最后的式子是: \(\frac{\left ( n - 2 \right )!}{\prod \left ( d[i] - 1 \right )! }\)
HNOI2004树的计数就是一道和和上面这个问题一模一样的题,实际上HNOI2008明明的烦恼也是同一道题(实在是不忍吐槽)。不过后者要写高精,我懒……
#include <bits/stdc++.h> using namespace std; #define maxn 1000 #define int long long int n, d[maxn], cal[maxn]; int sum, ans = 1, a[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } signed main() { n = read(); cal[0] = 1; for(int i = 1; i <= n; i ++) cal[i] = cal[i - 1] * i; for(int i = 1; i <= n; i ++) { d[i] = read(); if(d[i]) sum += d[i] - 1; if(d[i] == 0 && n != 1) { printf("0\n"); return 0; } a[i] = cal[d[i] - 1]; } if(sum != n - 2) { printf("0\n"); return 0; } sort(a + 1, a + 1 + n); int j = 1; while(a[j] == 1 && j < n) j ++; for(int i = 1; i <= n - 2; i ++) { ans *= i; while(j <= n && !(ans % a[j])) ans /= a[j], j ++; } printf("%lld\n", ans); return 0; }