题目:
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;
}