zoukankan      html  css  js  c++  java
  • 【HDU4661】Message Passing-思维+树形DP+组合数学

    测试地址:Message Passing
    题目大意: nn个人,每个人知道一条独一无二的信息,每次可以选择一个人,向与他有关系的一个人传递所有他已经知道的信息,关系网是树状的,目标是让所有人都知道所有的信息,问有多少种传递信息的方案,使得传递的次数最少。
    做法: 本题需要用到思维+树形DP+组合数学。
    首先,显然传递次数的下限是2(n1)2(n-1),那么我们能不能到达这个下限呢?如果能,怎么到达?其实容易观察出,我们可以先把所有信息都聚集在某一个人,然后再让信息从这个人开始传递到所有人,这样传递次数就能达到下界,可以证明所有最优解都满足这样的过程。
    于是现在就是求以某个人xx为中间的聚集点时,总的方案数是多少。我们发现,如果以xx为根,从儿子向父亲连边,那么从各点传递信息到xx的方案数,就等于拓扑序的数量。同理,从父亲向儿子连边时拓扑序的数量,就等于从xx传递信息到各点的方案数。那么根据乘法原理,整个过程的方案数只要把这两个方案数乘起来即可。易知,一个图和其反图的拓扑序数量是相同的,所以我们只要求从儿子向父亲连边时的方案数ansxans_x即可。
    我们先随便选一个点为根,假设是11,那么我们可以用树形DP求出ans1ans_1。具体来说,用f(i)f(i)表示以点ii为根的子树的方案数,那么在合并方案时,点ii一定是最后选,而它的各子树中又要满足各自的顺序,所以方案数实际上是:Csiz(i)1siz(son1)Csiz(i)1siz(son1)siz(son2)...f(soni)C_{siz(i)-1}^{siz(son_1)}cdot C_{siz(i)-1-siz(son_1)}^{siz(son_2)}cdot...cdot prod f(son_i),其中siz(x)siz(x)表示以xx为根子树中的点数,把组合数拆开简化,这个式子可以写成:
    f(i)=siz(i)!f(soni)siz(soni)!f(i)=siz(i)!cdot prod frac{f(son_i)}{siz(son_i)!}
    预处理阶乘就可以O(1)O(1)转移了,这样我们就能O(n)O(n)地算出ans1ans_1了。
    但如果计算每个ansxans_x都要O(n)O(n),总的时间复杂度也是受不了的,因此我们使用经典的方法——换根。还是先以11为整棵树的根,令fa(x)fa(x)xx的父亲,假设ansfa(x)ans_{fa(x)}已经算出,如何计算ansxans_x
    要计算这个东西,首先当以xx为根时,它在以11为根的树中的儿子现在依然是它的儿子,区别就是多了一棵子树,这棵子树的形态和在以fa(x)fa(x)为根的树中去掉以xx为根的子树相同。因此,我们只要在ansfa(x)ans_{fa(x)}中消掉以xx为根子树的贡献,然后将这个值作为正常子树对ansxans_x转移即可,因为要求某个f(i)f(i)的逆元,所以转移是O(logMod)O(log Mod)的。这也是为什么上面的转移式子要写成那样的原因:更加容易看出应该消掉哪个部分的贡献。于是DP的复杂度就是O(nlogMod)O(nlog Mod)了,这个问题就解决了。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll mod=1000000007;
    int T,n,first[1000010],tot,fa[1000010]={0},siz[1000010];
    ll fac[1000010],inv[1000010],invfac[1000010];
    ll f[1000010],ans[1000010];
    struct edge
    {
    	int v,next;
    }e[2000010];
    
    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 s=1,ss=a;
    	while(b)
    	{
    		if (b&1) s=s*ss%mod;
    		ss=ss*ss%mod;b>>=1;
    	}
    	return s;
    }
    
    void dp1(int v)
    {
    	siz[v]=f[v]=1;
    	for(int i=first[v];i;i=e[i].next)
    		if (e[i].v!=fa[v])
    		{
    			fa[e[i].v]=v;
    			dp1(e[i].v);
    			siz[v]+=siz[e[i].v];
    			f[v]=f[v]*invfac[siz[e[i].v]]%mod*f[e[i].v]%mod;
    		}
    	f[v]=f[v]*fac[siz[v]-1]%mod;
    }
    
    void dp2(int v)
    {
    	if (v!=1)
    	{
    		ll faf=ans[fa[v]];
    		int fasiz=n-siz[v];
    		faf=faf*fac[siz[v]]%mod*power(f[v],mod-2)%mod;
    		faf=faf*invfac[n-1]%mod*fac[fasiz-1]%mod;
    		ans[v]=f[v]*invfac[fasiz]%mod*faf%mod;
    		ans[v]=ans[v]*invfac[siz[v]-1]%mod*fac[n-1]%mod;
    	}
    	else ans[v]=f[v];
    	for(int i=first[v];i;i=e[i].next)
    		if (e[i].v!=fa[v]) dp2(e[i].v);
    }
    
    int main()
    {
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d",&n);
    		
    		tot=0;
    		for(int i=1;i<=n;i++)
    			first[i]=0;
    		fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    		for(ll i=2;i<=n;i++)
    		{
    			fac[i]=fac[i-1]*i%mod;
    			inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    			invfac[i]=invfac[i-1]*inv[i]%mod;
    		}
    		
    		for(int i=1;i<n;i++)
    		{
    			int a,b;
    			scanf("%d%d",&a,&b);
    			insert(a,b),insert(b,a);
    		}
    		
    		dp1(1);
    		dp2(1);
    		ll totans=0;
    		for(int i=1;i<=n;i++)
    			totans=(totans+ans[i]*ans[i])%mod;
    		printf("%lld
    ",totans);
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    HDU 5521 Meeting
    HDU 5170 GTY's math problem
    HDU 5531 Rebuild
    HDU 5534 Partial Tree
    HDU 4101 Ali and Baba
    HDU 5522 Numbers
    HDU 5523 Game
    ZUFE OJ 2301 GW I (3)
    POJ 2398 Toy Storage
    POJ 2318 TOYS
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793249.html
Copyright © 2011-2022 走看看