题意:给定一棵有n个节点的树,现在要给节点附1~n的权值(各节点权值不能相同),一棵子树的领袖就是子树中权值最大的节点,问有多少种分配方案使得最后有恰好K个领袖。
解法:这道题一看以为是树上的计数问题,想了好久的树形DP没想到,最后看题解才知道解法是概率DP(qwq)。解法还是非常巧妙的,感觉自己现在还没理解到它的精髓。
先求出这棵树以i点位根的子树的结点个数son[i],然后就不用管这棵树了。设dp[i][j]为前i棵子树恰好有j个领袖的概率。
因为问的是方案数,所以随机选,选择没有优劣之分每个点都是等概率的,那么我们可以写出dp方程。
dp[i][j]=(dp[i-1][j]*(son[i]-1)/son[i] , dp[i-1][j-1]*1/son[i] ) 前面代表第i个点就是领袖后面代表第i个点不是领袖。
注意这是个线性递推方程式,此时这里的i是线性的,与树的结构已经没有关系了。
求出dp[n][k]之后,答案就是dp[n][k]*n!
代码要注意因为每次都要除以son[i]所以难免要求逆元,因为状态多达1000^2个所以不先把逆元预处理出来会TLE。
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=1e3+10; const int P=1e9+7; int n,k,son[N]; int dp[N][N]; vector<int> G[N]; LL power(LL x,LL p) { LL ret=1; for (;p;p>>=1) { if (p&1) ret=ret*x%P; x=x*x%P; } return ret; } void dfs(int x,int fa) { son[x]=1; for (int i=0;i<G[x].size();i++) { int y=G[x][i]; if (y==fa) continue; dfs(y,x); son[x]+=son[y]; } } int inv[N]; void prework() { for (int i=1;i<=1000;i++) inv[i]=power(i,P-2); } int main() { prework(); int T,cas=0; cin>>T; while (T--) { scanf("%d%d",&n,&k); for (int i=1;i<=n;i++) G[i].clear(); for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); G[x].push_back(y); G[y].push_back(x); } for (int i=1;i<=n;i++) son[i]=0; dfs(1,0); for (int i=0;i<=n;i++) for (int j=0;j<=n;j++) dp[i][j]=0; dp[0][0]=1; for (int i=1;i<=n;i++) for (int j=0;j<=min(i,k);j++) { if (j<1) dp[i][j]=(LL)dp[i-1][j]*(son[i]-1)%P*inv[son[i]]%P; else dp[i][j]=((LL)dp[i-1][j]*(son[i]-1)%P*inv[son[i]]%P+(LL)dp[i-1][j-1]*1*inv[son[i]]%P)%P; } int ans=dp[n][k]; for (int i=1;i<=n;i++) ans=((LL)ans*i)%P; printf("Case #%d: %d ",++cas,ans); } return 0; }