Description

一个有 \(n\) 个结点的树, 设它的结点分别为 \(v_1, v_2, \ldots, v_n\), 已知第 \(i\) 个结点 \(v_i\) 的度数为 \(d_i\), 问满足这样的条件的不同的树有多少棵. 给定 \(n, d_1, d_2, \ldots, d_n\), 编程需要输出满足 \(d_{v_i} = d_i\) 的树的个数.

Input

第一行是一个正整数 \(n\), 表示树有 \(n\) 个结点. 第二行有 \(n\) 个数, 第 \(i\) 个数表示 \(d_i\), 即树的第 \(i\) 个结点的度数. 其中\(1 \leq n \leq 150\), 输入数据保证满足条件的树不超过 \(10^{17}\) 个.

Output

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

Sample Input

4
2 1 2 1

Sample Output

2

Explanation

此题做法类同 bzoj1005, 具体如下, 摘自黄学长博客:http://hzwer.com/3272.html

该题运用到了树的 Prüfer 编码的性质:

  1. 树的 Prüfer 编码的实现: 不断删除树中度数为 \(1\) 的最小序号的点, 并输出与其相连的节点的序号, 直至树中只有两个节点.
  2. 通过观察我们可以发现: 任意一棵 \(n\) 节点的树都可唯一的用长度为 \(n-2\) 的 Prüfer 编码表示, 度数为 \(m\) 的节点的序号在 Prüfer 编码中出现的次数为 \(m-1\).
  3. 怎样将 Prüfer 编码还原为一棵树: 从 Prüfer 编码的最前端开始扫描节点, 设该节点序号为 \(u\), 寻找不在 Prüfer 编码的最小序号且没有被标记的节点 \(v\), 连接 \(u, v\) 并标记 \(v\), 将 \(u\) 从 Prüfer 编码中删除, 扫描下一节点.

该题需要将树转化为 Prüfer 编码:

\(n\) 为树的节点数,\(d_i\) 为各节点的度数,\(m\) 为无限制度数的节点数, 则:\[ tot = \sum_{i=1}^{n} d_i - 1 \] 所以要求在 \(n-2​\) 大小的数组中插入 \(tot​\) 各序号, 共有 \(C_{n-2}^{tot}​\) 种插法;

\(tot\) 各序号排列中, 插第一个节点的方法有 \(C_{tot}^{d_1-1}\) 种插法;

插第二个节点的方法有 \(C_{tot-(d_1-1)}^{d_2-1}\) 种插法;

………

另外还有 \(m\) 各节点无度数限制, 所以它们可任意排列在剩余的 \(n-2-tot\) 的空间中, 排列方法总数为 \(m^{n-2-tot}\);

根据乘法原理:\[ \begin{aligned} ans &= C_{n-2}^{tot} \cdot C_{tot}^{d_1-1} \cdot C_{tot-(d_1-1)}^{d_2-1} \cdot \ldots \cdot c_{d_n-1}^{d_n-1} \cdot m^{n-2-tot}\\ &\Leftrightarrow \frac{(n-2)!}{(n-2-tot)!tot!} \cdot \frac{tot!}{(d_1-1)!(tot-d_1+1)!} \cdot \ldots \cdot \frac{(d_n-1)!}{(d_n-1)!} \cdot m^{n-2-tot}\\ &\Leftrightarrow \frac{(n-2)! \cdot m^{n-2-tot}}{(n-2-tot)! \cdot (d_1-1)! \cdot (d_2-1)! \cdot \ldots \cdot (d_n-1)!} \end{aligned} \]

然后就要高精度了...... 但高精度除法太麻烦了, 显而易见的排列组合一定是整数, 所以可以进行质因数分解, 再做一下相加减.

关于 \(n!\) 质因数分解有两种方法, 第一种暴力分解, 这里着重讲第二种.

\(p\) 为质数, 则 \(n!\) 可分解为 一个数 \(\times p^x\), 其中:\[ x = \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \ldots \left\lfloor \frac{n}{p^t} \right\rfloor (p_t \lt n) \]

\[ \therefore n! = \prod_{i=1}^{m} p_i^{x_i} (p_m \lt n) \]

然后我们把这道题的结论推广到这里:

  • 当某些节点不联通时, 一定无法生成一棵树.
  • 当总连通度之和加起来不等于 \(2n-2\) 时, 根本就不是一棵树.

特判完毕以后套用上面那道题公式就可以了.

Source Code


#include <iostream>
#include <cstdlib>
#include <cstdio>

using namespace std;
typedef long long lli;
const int maxn = 210;

lli qpow(lli a, lli b)
{
    lli c = 1;
    for (; b > 0; b >>= 1, a *= a)
        if (b & 1)
            c *= a;
    return c;
}

int comp[maxn];
void factor(lli x, int flag)
{
    for (lli i = 2; i * i <= x; i++)
        while (x % i == 0)
            comp[i] += flag,
            x /= i;
    if (x > 0)
        comp[x] += flag;
    return ;
}

int n;
int v[maxn];

int main(int argc, char** argv)
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &v[i]);
    // Attempting to really do some decompositions
    for (int i = 2; i <= n - 2; i++)
        factor(i, 1);
    // Checking if all are not right
    lli sum = 0;
    for (int i = 1; i <= n; i++) {
        if (v[i] <= 0 && n != 1) {
            printf("0\n");
            return 0;
        }
        sum += v[i] - 1;
        for (int j = 2; j <= v[i] - 1; j++)
            factor(i, -1);
    }
    if (sum != n - 2) {
        printf("0\n");
        return 0;
    }
    // Invoking fast exponentiation
    lli res = 1;
    for (int i = 1; i <= n - 2; i++)
        if (v[i] > 0)
            res *= qpow(i, v[i]);
    printf("%lld\n", res);
    return 0;
}