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\n",ans);
}
int main(){
	int t;
	pre_fac(1000000);
	scanf("%d",&t);
	while(t--)solve();
	return 0;
}

posted @ 2020-04-06 18:19  tttttttttrx  阅读(166)  评论(1编辑  收藏  举报