• [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数列+可重集排列+阶乘质因数分解
  • 相关阅读:
    计算机网络知识技能水平的测评试题
    Socket与系统调用深度分析
    学习构建调试Linux内核网络代码的环境MenuOS系统
    深入学习socket网络编程,以java语言为例
    网络配置工具iproute2和net-tools的基本原理和基本使用方法
    Linux系统学习总结报告
    结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
    深入理解系统调用-40号调用
    基于mykernel2.0 编写一个操作系统内核
    交互式多媒体图书平台的设计与实现
  • 原文地址:https://www.cnblogs.com/Asm-Definer/p/4164947.html
Copyright © 2020-2023  润新知