【算法】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;
}

 

posted @ 2018-05-27 16:10  Twilight_Sx  阅读(370)  评论(0编辑  收藏  举报