[HNOI 2004]树的计数

Description

一个有n个结点的树,设它的结点分别为v1, v2, …, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵。给定n,d1, d2, …, dn,编程需要输出满足d(vi)=di的树的个数。

Input

第一行是一个正整数n,表示树有n个结点。第二行有n个数,第i个数表示di,即树的第i个结点的度数。其中1<=n<=150,输入数据保证满足条件的树不超过10^17个。

Output

输出满足条件的树有多少棵。

Sample Input

4
2 1 2 1

Sample Output

2

题解

$Prüfer$编码&$Cayley$公式。

预备知识:->戳我<-

这里谈下自己的理解:

(此段与题目无关,可选择跳过)首先对于$Cayley$公式,其实讲的就是“$n$阶完全图生成数个数为$n^{n-2}$”,换言之就是“$n$个带编号顶点的无根生成树共$n^{n-2}$个”。

证明:这里引用$Prüfer$编码,不了解的话可以戳上文链接。其实就是对于任何一棵无根生成树,都有一个长度为$n-2$的序列。这个序列是这样定义的:每次在叶节点中找到一个编号最小的节点,将其删去,记录下相邻节点。因为是无根,若顶点只有$2$个,显然只有一棵树,长度就是$n-2$。

而对于任何一个$Prüfer$编码都能够还原成一棵无根树。

我们回到这道题,我们拥有这样一个结论:“任何一个$Prüfer$编码都能够还原成一棵无根树”。

那么我们就可以用$Prüfer$编码来解决问题。

我们发现第$i$个点会在$Prüfer$编码中出现$d[i]-1$次:因为自己“被删”需要$1$个度,他的其他相邻节点“被删”要$d[i]-1$个度。

那么等于说$i$这个数会在编码中出现$d[i]-1$次。

因为数列长度为$n-2$,我们看有序排列:总共有$(n-2)!$个

考虑去重:因为此时$Prüfer$编码中的数字$i$恰好出现$d[i]-1$次我们只需要对于每个$i$都除以$(d[i]-1)!$就可以了。

所以答案就是这里写图片描述

注意要特殊讨论构成不了树的情况。

 1 //It is made by Awson on 2017.10.6
 2 #include <map>
 3 #include <set>
 4 #include <cmath>
 5 #include <ctime>
 6 #include <queue>
 7 #include <stack>
 8 #include <vector>
 9 #include <cstdio>
10 #include <string>
11 #include <cstdlib>
12 #include <cstring>
13 #include <iostream>
14 #include <algorithm>
15 #define LL long long
16 #define Max(a, b) ((a) > (b) ? (a) : (b))
17 #define Min(a, b) ((a) < (b) ? (a) : (b))
18 #define sqr(x) ((x)*(x))
19 using namespace std;
20 void read(int &x) {
21   char ch; bool flag = 0;
22   for (ch = getchar(); !isdigit(ch) && ((flag |= (ch == '-')) || 1); ch = getchar());
23   for (x = 0; isdigit(ch); x = (x<<1)+(x<<3)+ch-48, ch = getchar());
24   x *= 1-2*flag;
25 }
26 
27 int n, a[155];
28 int cnt[155];
29 int pre[155];
30 
31 void prepare() {
32   bool isprime[155];
33   int q[155], top = 0;
34   memset(isprime, 1, sizeof (isprime));
35   isprime[1] = 0;
36   for (int i = 2; i <= n; i++) {
37     if (isprime[i]) q[++top] = i;
38     for (int j = 1; j <= top && i*q[j] <= n; j++) {
39       pre[i*q[j]] = q[j];
40       isprime[i*q[j]] = 0;
41       if (i%q[j] == 0) break;
42     }
43   }
44 }
45 void noanswer() {
46   printf("0\n");
47   exit(0);
48 }
49 void work() {
50   read(n);
51   prepare();
52   int sum = 0;
53   for (int i = 1; i <= n; i++) {
54     read(a[i]);
55     if (!a[i] && n != 1) noanswer();
56     a[i]--; sum += a[i];
57   }
58   if (sum != n-2) noanswer();
59   for (int i = 2; i <= n-2; i++) {
60     int j = i;
61     while (pre[j]) {
62       cnt[pre[j]]++;
63       j /= pre[j];
64     }
65     cnt[j]++;
66   }
67   for (int i = 1; i <= n; i++)
68     for (int j = 2; j <= a[i]; j++) {
69       int k = j;
70       while (pre[k]) {
71     cnt[pre[k]]--;
72     k /= pre[k];
73       }
74       cnt[k]--;
75     }
76   LL ans = 1;
77   for (int i = 2; i <= n; i++)
78     for (int j = 1; j <= cnt[i]; j++)
79       ans *= i;
80   printf("%lld\n", ans);
81 }
82 int main() {
83   work();
84   return 0;
85 }

 

转载于:https://www.cnblogs.com/NaVi-Awson/p/7631762.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值