[BZOJ1005][HNOI2008]明明的烦恼(prufer计数+组合数)

prufer序列链接:https://www.cnblogs.com/dirge/p/5503289.html
可以发现,这题是一个无根树计数的模型,可以利用prufer序列求解。如果不考虑每一个点的度数限制,那么结果就是 nn2 ,而根据prufer序列的构造方法能够得到,对于任意一个点 i i在prufer序列中的出现次数,等于点 i 的度数减1
这时候,就变成了在 n2 个值中选 D11 个值,定为 1 ,再在剩下的n1D1中选 D21 个值,定为 2 ,然后在剩下的nD1D2中选出 D31 个值定为 3 ,……,最后还没确定的值,可以定为1 n <script type="math/tex" id="MathJax-Element-16">n</script>中的任意数值。
这本来可以用组合数简单解决,然而这题偏偏是高精……
高精求组合数,可以用质因数分解的方法计算。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1005, M = 905, ZZQ = 1e6;
int n, m, sm, dm, d[N], cnt[N], pn, pri[N], pw[N][N], sum[N][N];
bool vis[N];
void calc(int n, int sgn) {
    int i; for (i = 1; i <= pn; i++)
        cnt[i] += sgn * sum[n][i];
}
struct cyx {
    int n; ll a[N];
    cyx() {}
    cyx(int _n) :
        n(_n) {memset(a, 0, sizeof(a));}
};
cyx num(int a) {
    cyx res = cyx(1); res.a[1] = a;
    return res;
}
cyx prod(cyx a, cyx b) {
    int i, j; cyx res = cyx(a.n + b.n + 2);
    for (i = 1; i <= a.n; i++) for (j = 1; j <= b.n; j++)
        res.a[i + j - 1] += a.a[i] * b.a[j];
    for (i = 1; i <= res.n; i++) res.a[i + 1] += res.a[i] / ZZQ,
    res.a[i] %= ZZQ; while (res.n > 1 && !res.a[res.n]) res.n--;
    return res;
}
cyx qpow(cyx a, int b) {
    cyx res = num(1);
    while (b) {
        if (b & 1) res = prod(res, a);
        a = prod(a, a);
        b >>= 1;
    }
    return res;
}
void write(cyx a) {
    int i; for (i = a.n; i; i--)
        if (i == a.n) printf("%d", a.a[i]);
        else printf("%06d", a.a[i]);
    printf("\n");
}
int main() {
    int i, j; n = read(); for (i = 1; i <= n; i++) d[i] = read();
    for (i = 2; i * i <= 1000; i++) {
        if (vis[i]) continue;
        for (j = i * i; j <= 1000; j += i)
            vis[j] = 1;
    }
    for (i = 2; i <= 1000; i++) if (!vis[i]) pri[++pn] = i;
    for (i = 1; i <= 1000; i++) for (j = 1; j <= pn; j++) {
        int x = i; while (x % pri[j] == 0) pw[i][j]++, x /= pri[j];
        sum[i][j] = sum[i - 1][j] + pw[i][j];
    }
    if (n == 1) return printf("%d\n", (d[1] == 0 || d[1] == -1)), 0;
    if (n == 2) return printf("%d\n", ((d[1] == 1 || d[1] == -1)
        && (d[2] == 1 || d[2] == -1))), 0;
    for (i = 1; i <= n; i++) if (d[i] < -1 || d[i] == 0) return puts("0"), 0;
    for (i = 1; i <= n; i++) if (d[i] != -1) sm += (--d[i]); else m++;
    if (sm > n - 2 || (!m && sm != n - 2)) return puts("0"), 0;
    dm = n - 2; sm = dm - sm; calc(dm, 1);
    for (i = 1; i <= n; i++) if (d[i] != -1) calc(d[i], -1), dm -= d[i];
    calc(dm, -1); cyx ans = num(1);
    for (i = 1; i <= pn; i++) ans = prod(ans, qpow(num(pri[i]), cnt[i]));
    ans = prod(ans, qpow(num(m), sm)); write(ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值