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;
}