hdu 4661 Message Passing(树形dp)

179 篇文章 0 订阅

题目链接:hdu 4661 Message Passing

解题思路

考虑以每个位置为第一个汇总的点,即根节点。然后类似村名排队的计数。

代码

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int maxn = 1000000;

int fac[maxn + 5], inv[maxn + 5];

int pow_mod(ll x, int n) {
    ll ret = 1;
    while (n) {
        if (n&1) ret = ret * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return ret;
}

void presolve() {
    fac[0] = 1;
    for (int i = 1; i <= maxn; i++)
        fac[i] = 1LL * i * fac[i-1] % mod;
    inv[maxn] = pow_mod(fac[maxn], mod-2);
    for (int i = maxn-1; i >= 0; i--)
        inv[i] = 1LL * (i+1) * inv[i+1] % mod;
}

int N, ans, sz[maxn + 5];
vector<int> G[maxn + 5];
ll dp[maxn + 5];

ll C(int n, int k) {
    if (k > n || k < 0 || n < 0) return 0;
    return 1LL * fac[n] * inv[k] % mod * inv[n-k] % mod;
}

ll dfs (int u, int f) {
    ll& ret = dp[u];
    ret = 1;
    sz[u] = 0;

    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (v == f) continue;
        ret = ret * dfs(v, u) % mod;
        sz[u] += sz[v];
    }

    int s = sz[u];
    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (v == f) continue;
        ret = ret * C(s, sz[v]) % mod;
        s -= sz[v];
    }
    sz[u]++;
    return ret;
}

void dfs(int u, int f, ll k) {
    // k 为以u为根对应的反向树有多少种方式
    ll ret = k;
    k = (k * dp[u]) % mod;

    int s = N-1;
    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (v == f) continue;

        // 扣除现在枚举的子树
        ll iv = k * pow_mod(dp[v] * C(sz[u]-1, sz[v]) % mod, mod-2) % mod;
        dfs(v, u, iv * C(N-sz[v]-1, sz[u]-sz[v]-1) % mod);
        ret = ret * C(s, sz[v]) % mod * dp[v] % mod;
        s -= sz[v];
    }
    ans = (ans + ret * ret % mod) % mod;
}

int main () {
    presolve();
    int cas;
    scanf("%d", &cas);
    while (cas--) {
        scanf("%d", &N);
        int u, v;
        for (int i = 1; i <= N; i++) G[i].clear();
        for (int i = 1; i < N; i++) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }

        ans = 0;
        dfs(1, 0);
        dfs(1, 0, 1);
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值