zoukankan      html  css  js  c++  java
  • HDU4661 Message Passing 换根dp 组合数学

    HDU4661 Message Passing 换根dp

    题意

    给一棵树,每个点都有一个独一无二得消息,每次可以让一个点把消息传递到相邻得点,这样这个相邻得点就知道了这个消息。一个点传递的时候,会把它知道的所有的消息都传递。问满足最小传递次数的方案种类有多少

    ps:换根的时候换得人都要傻了,后面看了一眼大佬博客发现可以化简。。感谢下面这个大佬
    reference:https://www.cnblogs.com/zhsl/archive/2013/08/10/3250755.html

    题解

    感性得想一下,应该是要把所以信息都集中在一个点,再传出去最优,手跑以下样例发现很对,一来一回就是边权和的两倍,并且所有点都可以做这个中心点。
    但是本题是求方案数,我们定一个根节点,可以发现,每一个合法的集中的方案都有一个唯一的分散的方案相对应,也就是他们互为逆过程,所以可得集中的方案数=发散的方案数,那么对于一个集中点来说,方案总数就是集中的方案数*发散的方案树。
    由于这题对于每一个点都要求一遍,不难想到换根dp。
    我们先考虑对于一个固定点怎么求,方案数其实就是树上的拓扑序数。
    我们设dp[x]为以x为根节点的方案数,sz[x]为x子树大小,假如x节点有三个子节点u,v,w。我们先考虑u,v两个点有多少种情况,自底向上求,我们假设dp[u],dp[v]已知,那么这两个子树合并起来的方案数就是(dp[u]*dp[v]*C(sz[u]+sz[v],sz[v])),后面这个组合数怎么理解呢?(C(sz[u]+sz[v],sz[u]))相当于sz[u]+sz[v]个位置,选n个填入子树n,也就是相当于一共有(dp[u])个合法的拓扑序,我把(dp[v])个拓扑序和其相互插入,并且u,v,序列的点提出来顺序不变
    当多了一个w的时候情况又如果呢,我们可以把u,v合并的子树看作一个新的子树k,那么

    • (dp[k]=dp[u]*dp[v]*C(sz[u],sz[v],sz[v])),把k和合并有(dp[w]*dp[k]*C(sz[k]+sz[w],sz[w]))
      代入得
    • (dp[w]*dp[u]*dp[v]*C(sz[u],sz[v],sz[v])*C(sz[u]+sz[v]+sz[w],sz[w]))
      化简得
    • (frac{dp[u]*dp[v]*dp[w]*(sz[u]+sz[v]+sz[w])!}{sz[u]!*sz[v]!*sz[w]!})

    我们由上述公式可以得出多个子节点的时候dp[x]的计算公式为

    • (dp[x]=frac{(prod dp[son])*(sz[x]-1)!}{prod sz[son]!})

    由此 单次dp就求出来了。
    如何换根呢(这里不化简就麻烦得要死)
    我们设 x的父节点为fa
    由公式我们设t为fa节点去掉x这个子树的dp值 可得
    (t=frac{dp[fa]*sz[x]!*(n-1-sz[x])!}{(n-1)!*dp[x]})
    以x为根的dp为(dp[x]=frac{dp[x]*(n-1)!*t}{(sz[x]-1)!*(n-sz[x])!})

    代入t得
    (dp[x]=frac{dp[x]*(n-1)!*dp[fa]*sz[x]!*(n-1-sz[x])!}{(sz[x]-1)!*(n-sz[x])!*(n-1)!*dp[x]})
    化简得
    (dp[x]=frac{dp[fa]*sz[x]}{(n-sz[x])})
    化的头痛,不如下面解法二

    解法二:见ABC160 F - Distributing Integers 写起来简单多了。。

    解法一代码(2没写,跟那题差不多)

    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    #define F first
    #define S second
    #define mkp make_pair
    #define pii pair<int,int>
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int maxn=1e6+10;
    const int mod=1e9+7;
    int dp[maxn],sz[maxn];
    int n;
    vector<int>v[maxn];
    ll mul(ll a,ll b){
    	return ((a%mod)*(b%mod)+mod)%mod;
    }
    ll add(ll a,ll b){
    	return (a+b+mod)%mod;
    }
    ll fpow(ll a,ll b){
    	ll ans=1;
    	while(b){
    		if(b&1)ans=mul(ans,a);
    		a=mul(a,a);
    		b>>=1;
    	}
    	return ans;
    }
    int fac[maxn],inv[maxn],facinv[maxn];
    void pre_fac(int up){
    	//阶乘
    	fac[0]=fac[1]=1;
    	for(int i=2;i<=up;i++)fac[i]=mul(fac[i-1],i);
    	//阶乘逆元
    	facinv[up]=fpow(fac[up],mod-2);
    	for(int i=up-1;i>=0;i--)facinv[i]=mul(facinv[i+1],i+1);
    	// 数的逆元
    	inv[1] = 1;
    	for(int i = 2; i <=up; ++ i)
        inv[i] = mul((mod - mod / i) , inv[mod % i] );
    }
    
    ll C(ll n,ll m){//n>=m
    	return mul(facinv[n-m],mul(fac[n],facinv[m]));
    }
    ll ans=0;
    
    void dfs(int x,int fa){
    	dp[x]=sz[x]=1;
    	for(auto y:v[x]){
    		if(y==fa)continue;
    		dfs(y,x);
    		sz[x]+=sz[y];
    		dp[x]=mul(facinv[sz[y]],mul(dp[x],dp[y]));
    	}
    	dp[x]=mul(dp[x],fac[sz[x]-1]);
    }
    
    void reroot(int x,int fa){
    	dp[x]=mul(mul(dp[fa],sz[x]),inv[n-sz[x]]);
    	ans=add(ans,mul(dp[x],dp[x]));
    	for(auto y:v[x]){
    		if(y==fa)continue;
    		reroot(y,x);
    	}
    }
    void solve(){
    	ans=0;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)v[i].clear();
    	for(int i=1;i<n;i++){
    		int x,y;
    		scanf("%d%d",&x,&y);
    		v[x].pb(y);
    		v[y].pb(x);
    	}
    	dfs(1,-1);
    	ans=add(ans,mul(dp[1],dp[1]));
    	for(auto p:v[1]){
    		reroot(p,1);
    	}
    	printf("%lld
    ",ans);
    }
    int main(){
    	int t;
    	pre_fac(1000000);
    	scanf("%d",&t);
    	while(t--)solve();
    	return 0;
    }
    
    
  • 相关阅读:
    Python中如何取字典中的键值
    Python中random模块的用法案例
    Python中模块import的使用案例
    Python中模块的定义及案例
    Python中from … import …语句
    Python中模块调用说明
    Python中模块、类、函数、实例调用案例
    Python中读写文件三部曲
    Python中特殊函数__str__()
    Python--网络编程-----基于UDP协议的套接字不会发生粘包
  • 原文地址:https://www.cnblogs.com/ttttttttrx/p/12643414.html
Copyright © 2011-2022 走看看