[BZOJ1005](HNOI 2008)明明的烦恼
Description
自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树?
Input
第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1
Output
一个整数,表示不同的满足要求的树的个数,无解输出0
Sample Input
1
-1
-1
Sample Output
HINT
两棵树分别为1-2-3;1-3-2
分析
好久没有更新题解了。。。
很容易看出这是一道组合计数题。然而……如果没有图论基础是很难想出怎样构造的。。。不过我在今年四月份刚“入门”OI的时候有幸看到了省队RealCS的题解,提前接触到了带标号无根树计数的“prufer数列“>_<所以这次很快就写出了正解~(prufer数列详见Matrix67 的blog:http://www.matrix67.com/blog/archives/682)
首先,由prufer数列的性质我们知道:对于一棵给定的无根树,任意一个节点在这棵树的prufer数列中出现次数等于这个节点的度数 - 1。那么根据题目中的条件,我们就可以得到一个可重集排列问题:给定每个数字出现次数,求满足条件的排列个数。对于没有给定度数的节点,我们可以将它们用空格代替。我们需要在已有的数列中插入若干个空格,每个空格中填入任意一个“没有给定度数”的节点。这样,我们只需将得出的“可重集排列”数乘上空格数量的cnt次方(此处cnt表示没有给定度数的节点种数)即可。
那么,基本的思路确定了,我们现在的问题就是如何高效地计算可重集排列数了。根据可重集全排列公式,
$$P = \frac{N!}{\prod{n_i !}} $$其中$n_i$表示第i个元素的个数。麻烦的是,本题中全集规模N可能很大,这里的所有数都应当是高精度表示的,我们难道要一点一点做高精度除法吗?
作为一名强迫症患者,我无法容忍这样龟速的解法,我们需要想想怎样优化。首先,我们知道这个公式得出的一定是整数。不难想到我们可以对分子分母分别做质因数分解,再将上下得到的指数相减,最后统一乘入一个高精度整数即可。又考虑到这里分解的对象比较特殊(都是阶乘),我们可以找到一种更机智的分解方法:从小到大枚举素数,然后统计这个素数在2~n的每个整数中的指数之和即可。(详见代码中的"res"函数)
2 Problem: 1005
3 User: AsmDef
4 Language: C++
5 Result: Accepted
6 Time:20 ms
7 Memory:820 kb
8 ****************************************************************/
9
10 #include <cctype>
11 #include <cstdio>
12 #include <cmath>
13 #include <cstdlib>
14 inline void getd(int &x){
15 char c = getchar();
16 bool minus = 0;
17 while(!isdigit(c) && c != '-')c = getchar();
18 if(c == '-')minus = 1, c = getchar();
19 x = c - '0';
20 while(isdigit(c = getchar()))x = x * 10 + c - '0';
21 if(minus)x = -x;
22 }
23 /*======================================================*/
24 const int maxn = 1010;
25 struct BigN{
26 #define base 1000000
27 #define maxl 1000
28 int A[maxl], len;
29 BigN(){len = 1, A[0] = 0;}
30 BigN &operator *= (int x){
31 int i, mor = 0;
32 for(i = 0;i < len || mor;++i){
33 if(i < len)mor += A[i] * x;
34 A[i] = mor % base;
35 mor /= base;
36 }
37 if(i > len)len = i;
38 return *this;
39 }
40 }ans;
41 int N, S = 0, A[maxn], Acnt = 0, Bcnt = 0, prime[maxn], pcnt = 0;
42 inline void euler(){
43 int i, j;
44 bool not_p[maxn] = {0};
45 for(i = 2;i <= N;++i){
46 if(!not_p[i])prime[pcnt++] = i;
47 for(j = 0;j < pcnt;++j){
48 if(prime[j] * i > N)break;
49 not_p[prime[j]*i] = 1;
50 if(i % prime[j] == 0)break;
51 }
52 }
53 }
54 inline void init(){
55 getd(N);
56 if(N == 0){putchar('0');exit(0);}
57 int i, d;
58 if(N == 1){
59 getd(d);
60 if(d == -1 || !d)putchar('1');
61 else putchar('0');
62 exit(0);
63 }
64 for(i = 1;i <= N;++i){
65 getd(d);
66 if(d == 0){putchar('0');exit(0);}
67 if(d == -1) ++Bcnt;
68 else {
69 A[Acnt++] = d - 1;
70 S += d - 1;
71 }
72 }
73 if((S > N-2) || (S < N-2 && !Bcnt)){
74 putchar('0');
75 exit(0);
76 }
77 A[Acnt++] = N - 2 - S;
78 S = N - 2;
79 euler();
80 }
81 int powcnt[maxn] = {0};
82 inline void res(int n){
83 int i, j;
84 for(i = 0;i < pcnt;++i){
85 j = prime[i];
86 while(j <= n){
87 powcnt[i] -= n / j;
88 if(powcnt[i] < 0){printf("0");exit(0);}
89 j *= prime[i];
90 }
91 }
92 }
93 inline void work(){
94 int i, j = Acnt - 1, p;
95 ans.A[0] = 1;
96 for(i = 1;i <= A[j];++i)
97 ans *= Bcnt;
98 for(i = 0;i < pcnt;++i){
99 p = prime[i];
100 while(p <= S){
101 powcnt[i] += S / p;
102 p *= prime[i];
103 }
104 }
105 for(i = 0;i < Acnt;++i)
106 res(A[i]);
107 for(i = 0;i < pcnt;++i){
108 for(j = 1;j <= powcnt[i];++j)
109 ans *= prime[i];
110 }
111 i = ans.len - 1;
112 printf("%d", ans.A[i]);
113 while(i--)
114 printf("%06d", ans.A[i]);
115 }
116 int main(){
117 init();
118 work();
119 return 0;
120 }