[BZOJ1005](HNOI 2008)明明的烦恼

Description

自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树?

Input

第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1

Output

一个整数,表示不同的满足要求的树的个数,无解输出0

Sample Input

3
1
-1
-1

Sample Output

2

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"函数)

 

  1 /**************************************************************
  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 }
Prufer数列+可重集排列+阶乘质因数分解

 

posted @ 2014-12-15 15:18  Asm.Definer  阅读(273)  评论(0编辑  收藏  举报