【HDU5378】Leader in Tree Land-概率DP+逆元+好题

测试地址:Leader in Tree Land
题目大意:给定一棵有n个节点的以1号节点为根的有根树,现在要给节点附上1~n的权值(各节点权值不能相同),一棵子树的领袖就是子树中权值最大的节点,问有多少种分配方案使得最后有恰好K个领袖。
做法:本题是一道概率DP的题目。
这道题目从表面上看,怎么看都是树形DP啊,组合数学啊一堆乱糟糟的东西组合在一起的狂暴计数题,深入分析后也会发现树形DP的状态转移方程非常难找,因为想不到什么方法合并多个子树的方案数。其实,这道题是一道隐藏很深的概率DP,并且甚至都不用在树上做。
我们知道,总的赋值方案共有n!个,那么我们只需要求出使得有K个领袖的方案出现的概率,就可以算出方案数了。关于这一点,我们发现,每一个节点作为以它自己为根的子树的领袖的概率是相互独立的,为1/siz(i),其中siz(i)指以节点i为根的子树的节点个数。那么,我们可以设dp(i,j)为以节点1 i为根的子树中,有j个是领袖的概率,显然有状态转移方程:
dp(i,j)=dp(i1,j)×(siz(i)1)/siz(i)+dp(i1,j1)×1/siz(i)
边界条件是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;
}
posted @ 2017-09-25 19:33  Maxwei_wzj  阅读(123)  评论(0编辑  收藏  举报