可以用求概率的思想来解决这个问题。令以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!乘上概率就是答案。计算的时候用逆元代替上面的分数即可。
2 3 2 1 2 1 3 10 8 2 1 3 2 4 1 5 3 6 1 7 3 8 7 9 7 10 6
Case #1: 4 Case #2: 316512
#include <bits/stdc++.h> using namespace std; #define prt(k) cerr<<#k" = "<<k<<endl typedef long long ll; const ll inf = 0x3f3f3f3f; const ll mod = 1e9+7; const ll N = 1005; int sz[N]; vector<int> g[N]; ll dp[N][N]; int n, K; ll fac[N]; ll pmod(ll a, ll n) { ll r = 1; for (; n>0; n>>=1, a=a*a%mod) { if (n & 1) r = r * a % mod; } return r; } inline ll inv(ll a) { return pmod(a, mod - 2); } void dfs(int u, int fa) { sz[u] = 1; for (int v: g[u]) if (v-fa) { dfs(v, u); sz[u] += sz[v]; } } ll in[N]; int main() { fac[0] = 1; in[1] = 1; for (ll i=1;i<N;i++) fac[i]=fac[i-1]*i%mod, in[i]=inv(i); int re; scanf("%d", &re); int ca= 1; while (re--) { memset(sz,0 , sizeof sz); scanf("%d%d", &n ,&K); for(ll i=0;i<=n;i++) g[i].clear(); memset(dp, 0, sizeof dp); for (ll 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); 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; if (j > 0) dp[i][j] += 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; }