题目:http://acm.hdu.edu.cn/showproblem.php?pid=4661
题目大意:n个人的关系形成了一棵树,人为根节点,每个人都有一个独特的消息,每个人可以通过一步操作将一个消息传给和他连接的一个人,现在要求把所有的消息传遍所有人需要的最少步数的方案数。
思路:
好吧,表示先开始不会逆元,也不知道逆元是什么,只知道是个很神奇的东西。。 为了做这道题目,特意学了一下,发现真的很神奇,当然也是挺有用的~~
不过这里还有一个地方需要处理,第一遍dfs很好理解,算出了以i为根的子树的方案数,即这一遍只算出了1这个节点的方案数,第二遍dfs你需要把以所有节点的为根的方案数全加起来,当你dfs下来的时候,你可以顺便用d[ u ]把d[ v ] 更新了(u之后是v做根节点了),以后每次来一个节点只要+ d[ v ]*d[ v ]就行了,d[ v ] = d[ v ]*C(n-1,n-num[ v ]) * (d[ u ]/(C( n-1,num[ v ] )*d[ v ]) )。
代码如下:
#pragma comment(linker, "/STACK:10240000000000,10240000000000")
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 1000011;
int n;
typedef __int64 lld;
struct Edge
{
int t,next;
} edge[MAXN<<1];
int tot,head[MAXN];
void add_edge(int s,int t)
{
edge[tot].t = t;
edge[tot].next = head[s];
head[s] = tot++;
}
lld fac[MAXN];
void init()
{
fac[0] =1;
for(int i = 1;i<MAXN;i++)
fac[i] = fac[i-1]*i%MOD;
}
lld ex_gcd(lld a,lld b,lld &x,lld &y)
{
if(b==0)
{
x = 1;
y = 0;
return a;
}
lld tmp = ex_gcd(b,a%b,x,y);
lld t = x;
x = y;
y = t - a/b*y;
return tmp;
}
lld reverse(lld a,lld b)
{
lld x,y;
ex_gcd(a,b,x,y);
x = (x%b+b)%b;
return x;
}
lld cal(lld a,lld b)
{
return fac[a]*reverse(fac[b]*fac[a-b]%MOD,MOD)%MOD;
}
int num[MAXN];
lld d[MAXN];
void dfs1(int u,int fa)
{
num[u] = 1;
d[u] = 1;
for(int e = head[u];e!=-1;e = edge[e].next)
{
int v = edge[e].t;
if(v==fa) continue;
dfs1(v,u);
num[u] += num[v];
d[u] = cal(num[u]-1,num[v])*d[v]%MOD*d[u]%MOD;
}
}
lld ans ;
void dfs2(int u,int fa)
{
ans = (ans + d[u]*d[u]%MOD)%MOD;
for(int e = head[u];e!=-1;e = edge[e].next)
{
int v = edge[e].t;
if(v==fa) continue;
lld x = d[u]*reverse(d[v]*cal(n-1,num[v]),MOD)%MOD;
d[v] = d[v]*x%MOD*cal(n-1,n-num[v])%MOD;
dfs2(v,u);
}
}
int main()
{
init();
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int a,b;
tot = 0 ;
memset(head,-1,sizeof(head));
for(int i = 1;i<n;i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
ans = 0;
dfs1(1,-1);
dfs2(1,-1);
printf("%I64d\n",ans);
}
return 0;
}