hdu 4661 Message Passing(树形dp)

题意:比较容易懂,就是n个人,构成树形关系。每个人有一条独一无二的信息,每个人可以将自己的信息通过树边,共享给与他相邻的人,共享之后,被共享的人拥有他原有的信息和共享的来的信息。每次共享为一次操作,问每个人都拥有所有人的信息最小要的次数的共享方法有多少种。

解题思路:首先要明确的是,我们共享信息的策略是,将所有信息都共享给某一个人,再由这个人将所有信息反馈给其他人,这样共享信息的总操作次数是2*(n-1)(这一点自己YY下吧,我也证不了)。那怎么选择那个最中心的那个人呢,非常简单,任何一个人都可以。。为什么呢?自己画画就知道了。那么题目就转换成了,以某一点为根节点,从其他点将所有信息传到该根节点,有多少种方法,将任意节点为根,算出的答案累加起来,就是最终的方案数。我们要将所有信息都能传到根节点,那么对于某一节点来说,只有它所有子节点的信息都传到了该节点上,它才能往上传。这是不是跟拓扑排序很像?其实我们要求的就是拓扑序有多少种。定义dp[u]表示u节点以下,传到u节点的拓扑序有多少种,cnt[u]表示u有多少个子孙节点,f[i] = i!(i的阶乘),c[i][j]表示组合数。假设它有v1,v2,v3个节点,它们的拓扑序分别有dp[v1],dp[v2],dp[v3]这么多种。那么dp[u] = c[cnt[u]-1][cnt[v1]] * c[cnt[u]-1-cnt[v1]][cnt[v2]] * c[cnt[u]-1-cnt[v1]-cnt[v2]][cnt[v3]] * dp[v1] * dp[v2] * dp[v3](这个自己推推吧)。化简以后,得到dp[u] = f[cnt[u]-1] / ( f[cnt[v1]] * f[cnt[v2]] * f[cnt[v3]] ) * dp[v1] * dp[v2] * dp[v3] 。我们可以在o(n)的时间复杂度内算出以1节点为根的所有dp值(那么以1为根的答案就算出来了),以及其他一些辅助信息的值。然后按树的结构往下遍历,分别计算以其他节点为根的答案。当前根节点为1,那么我们要算以1的某一子节点为根的答案,其实就是将1往下旋转,将该子节点往上旋转(这是同步的)。这时该子节点成为根节点,1成为其子节点。我们将1成为子节点时的dp值重新算一遍(并不更新dp[1]),传递下去当做新的根的另一个儿子就可以了。旋转时对于新的根节点,他的儿子中,除了他的父亲节点变作子节点外,其他节点的信息都是不变的,因此我们可以用他的子节点的信息计算答案(新来的父亲节点也要算进去),而不需要重新从底部更新上来,总的复杂度还是o(n)的。(涉及到除法取模,要用到乘法逆元)

挫代码一份,不忍直视:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll __int64
using namespace std ;

const int maxn = 1111111 ;
const int mod = 1000000007 ;

struct Edge {
	int t , next ;
} edge[maxn<<1] ;
int head[maxn] , tot , cnt[maxn] ;
ll dp[maxn] , f[maxn] , ans ;

void new_edge ( int a , int b ) {
	edge[tot].t = b ;
	edge[tot].next = head[a] ;
	head[a] = tot ++ ;
	edge[tot].t = a ;
	edge[tot].next = head[b] ;
	head[b] = tot ++ ;
}

int n ;
void init () {
	int i ;
	for ( i = 0 ; i <= n ; i ++ ) {
		head[i] = -1 ;
		dp[i] = 1 ;
		cnt[i] = 1 ;
	}
	tot = ans = 0 ;
}

void ex_gcd ( ll a , ll b , ll &x , ll &y ) {
	if ( !b ) {
		x = 1 , y = 0  ;
		return ;
	}
	ex_gcd ( b , a % b , y , x ) ;
	y -= a / b * x ;
}

void cal ( int u , int fa ) {
	int i ;
	ll s = 1 , g = 1 , k , p ;
	for ( i = head[u] ; i != -1 ; i = edge[i].next ) {
		int v = edge[i].t ;
		if ( v == fa ) continue ;
		cal ( v , u ) ;
		cnt[u] += cnt[v] ;
	}
	s = f[cnt[u]-1] ;
	for ( i = head[u] ; i != -1 ; i = edge[i].next ) {
		int v = edge[i].t ;
		if ( v == fa ) continue ;
		s = s * dp[v] % mod ;
		g = g * f[cnt[v]] % mod ;
	}
	ex_gcd ( g , mod , k , p ) ;
	if ( k < 0 ) k += mod ;
	dp[u] = s * k % mod ;
}

void dfs ( int u , int fa , ll fv ) {
	int i , j ;
	ll k = f[cnt[1]-cnt[u]] , add = fv , p , ss = 1 , g ;
	for ( i = head[u] ; i != -1 ; i = edge[i].next ) {
		int v = edge[i].t ;
		if ( v == fa ) continue ;
		add = add * dp[v] % mod ;
		k = k * f[cnt[v]] % mod ;
	}
	ex_gcd ( k , mod , g , p ) ;
	if ( g < 0 ) g += mod ;
	ss = add ;
	add = add * f[cnt[1]-1] % mod * g % mod ;
	ans = ans + add * add % mod ;
	if ( ans >= mod ) ans -= mod ;
	for ( i = head[u] ; i != -1 ; i = edge[i].next ) {
		int v = edge[i].t ;
		if ( v == fa ) continue ;
		ll fuck , fuck1 ;
		ex_gcd ( dp[v] , mod , fuck , p ) ;
		if ( fuck < 0 ) fuck += mod ;
		fuck = ss * fuck % mod * f[cnt[1]-cnt[v]-1] % mod ;
		ex_gcd ( f[cnt[v]] , mod , fuck1 , p ) ;
		if ( fuck1 < 0 ) fuck1 += mod ;
		fuck1 = k * fuck1 % mod ;
		ex_gcd ( fuck1 , mod , g , p ) ;
		if ( g < 0 ) g += mod ;
		fuck = fuck * g % mod ;
		dfs ( v , u , fuck ) ;
	}
}

int main () {
	int cas , i , j , a , b ;
	ll k , t , g , p ;
	f[0] = 1 ;
	for ( i = 1 ; i <= 1000001 ; i ++ ) f[i] = f[i-1] * i % mod ;
	scanf ( "%d" , &cas ) ;
	while ( cas -- ) {
		scanf ( "%d" , &n ) ;
		init () ;
		for ( i = 1 ; i < n ; i ++ ) {
			scanf ( "%d%d" , &a , &b ) ;
			new_edge ( a , b ) ;
		}
		cal ( 1 , 0 ) ;
		ans = dp[1] * dp[1] % mod ;
		k = t = 1 ;
		for ( i = head[1] ; i != -1 ; i = edge[i].next ) {
			int v = edge[i].t ;
			k = k * f[cnt[v]] % mod , t = t * dp[v] % mod ;
		}
		for ( i = head[1] ; i != -1 ; i = edge[i].next ) {
			int v = edge[i].t ;
			ll s = f[cnt[1]-cnt[v]-1] * t % mod ;
			ex_gcd ( dp[v] , mod , g , p ) ;
			if ( g < 0 ) g += mod ;
			s = s * g % mod ;
			ex_gcd ( f[cnt[v]] , mod , g , p ) ;
			if ( g < 0 ) g += mod ;
			ll fuck = k * g % mod ;
			ex_gcd ( fuck , mod , g , p ) ;
			if ( g < 0 ) g += mod ;
			s = s * g % mod ;
			dfs ( v , 1 , s ) ;
		}
		printf ( "%I64d\n" , ans ) ;
	}
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值