【HDU4661】Message Passing-思维+树形DP+组合数学

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

#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\n",totans);
	}
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值