**HDU 5378 - Leader in Tree Land(概率DP)

题目:

http://acm.hdu.edu.cn/showproblem.php?pid=5378

题意:

给你一棵树,有n个人,标号分别是1~n,每个节点放一个人。对于一棵子树来说,标号最大的那个人为领导,求出领导为k个的方案数。

思路:

直接复制题解思路,奇妙的想法!

可以用求概率的思想来解决这个问题。令以i号节点为根的子树为第i棵子树,设这颗子树恰好有sz[i]个点。那么第i个点是第i棵子树最大值的概率为1/sz[i],不是最大值的概率为(sz[i]-1)/sz[i]。现在可以求解恰好有k个最大值的概率。

令dp[i][j]表示考虑编号从1到i的点,其中恰好有j个点是其子树最大值的概率。 很容易得到如下转移方程:dp[i][j]=dp[i-1][j]*(sz[i]-1)/sz[i]+dp[i-1][j-1]/sz[i]。这样dp[n][k]就是所有点中恰好有k个最大值的概率。

题目要求的是方案数,用总数n!乘上概率就是答案。计算的时候用逆元代替上面的分数即可

AC.

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>

using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 1005;
vector<int> g[maxn];
ll fac[maxn], in[maxn];
ll dp[maxn][maxn];

ll pmod(ll a, ll n)
{
    ll r = 1;
    while(n > 0) {
        if(n & 1) r = r*a % mod;
        a = a*a %mod;
        n >>= 1;
    }
    return r;
}
int sz[maxn];
void dfs(int u, int fa)
{
    sz[u] = 1;
    for(int i = 0; i < g[u].size(); ++i) {
        int v = g[u][i];
        if(v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
    }
}
int main()
{
    //freopen("in", "r", stdin);
    fac[0] = 1;
    in[1] = 1;
    for(int i = 1; i < maxn; ++i) {
        fac[i] = fac[i-1]*i%mod;
        in[i] = pmod(i, mod-2); //ÄæÔª
    }

    int T, ca = 1;
    scanf("%d", &T);

    while(T--) {
        int n, k;
        scanf("%d %d", &n, &k);

        for(int i = 0; i <= n; ++i) {
            g[i].clear();
        }
        memset(sz, 0, sizeof(sz));

        for(int i = 0; i < n-1; ++i) {
            int u, v;
            scanf("%d %d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }

        dfs(1, 1);

        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;
        for(int i = 1; i <= n; ++i) {
            for(int j = 0; j <=k; ++j) {

                dp[i][j] = dp[i-1][j] * (sz[i] - 1) %mod * in[sz[i]] % mod
                        + dp[i-1][j-1] * in[sz[i]] % mod;

                dp[i][j] %= mod;
            }
        }

        ll ans = dp[n][k] * fac[n] % mod;
        printf("Case #%d: %I64d\n", ca++, ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值