zoukankan      html  css  js  c++  java
  • 【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
    ",t,dp[n][k]); 
        }
    
        return 0;
    }
  • 相关阅读:
    PLSQL13
    01.Spring引入
    验证码重构
    短信验证码登录思路
    记住我 token保存到数据库
    图形验证码及其重构
    个性化用户认证流程
    01.Spring Security初识,表单认证
    堆和栈的区别
    系统分析与设计第二次作业
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793578.html
Copyright © 2011-2022 走看看