测试地址:Leader in Tree Land
题目大意:给定一棵有
n
个节点的以
做法:本题是一道概率DP的题目。
这道题目从表面上看,怎么看都是树形DP啊,组合数学啊一堆乱糟糟的东西组合在一起的狂暴计数题,深入分析后也会发现树形DP的状态转移方程非常难找,因为想不到什么方法合并多个子树的方案数。其实,这道题是一道隐藏很深的概率DP,并且甚至都不用在树上做。
我们知道,总的赋值方案共有
边界条件是
dp(0,0)=1
,最后的答案为
dp(n,K)×n!
。那么我们只需要
O(n)
预处理出
siz(i)
,然后
O(n2)
算出
dp(n,K)
即可完成此题。要注意的是,最后答案要模一个大质数,所以将以上的除以分母都改成乘以分母的逆元即可。
综合来说,本题的题面具有强大的迷惑性,第一是这个题目出在树的模型上,就很容易把选手的思路导向树算法上,第二就是这个题目是一个计数的题目,一般很难想到用总数乘以概率算方案数,所以这个题目还是很巧妙的,学习了。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000000007
using namespace std;
int T,n,k,first[1010],tot;
ll inv[1010],siz[1010],dp[1010][1010];
struct edge {int v,next;} e[2010];
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
ll power(ll a,ll b)
{
ll ans=1,s=a;
while(b)
{
if (b&1) ans=(ans*s)%mod;
b>>=1;s=(s*s)%mod;
}
return ans;
}
void dfs(int v,int f)
{
siz[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=f)
{
dfs(e[i].v,v);
siz[v]+=siz[e[i].v];
}
}
int main()
{
scanf("%d",&T);
for(int i=1;i<=1000;i++)
inv[i]=power((ll)i,mod-2);
for(int t=1;t<=T;t++)
{
memset(first,0,sizeof(first));
tot=0;
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
dfs(1,0);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
dp[i][0]=(((dp[i-1][0]*(siz[i]-1))%mod)*inv[siz[i]])%mod;
for(int j=1;j<=i;j++)
{
dp[i][j]=(((dp[i-1][j]*(siz[i]-1))%mod)*inv[siz[i]])%mod;
dp[i][j]=(dp[i][j]+dp[i-1][j-1]*inv[siz[i]])%mod;
}
}
for(int i=1;i<=n;i++) dp[n][k]=(dp[n][k]*i)%mod;
printf("Case #%d: %lld\n",t,dp[n][k]);
}
return 0;
}