码迷,mamicode.com
首页 > 其他好文 > 详细

hdu 4661 Message Passing(树形DP&组合数学)

时间:2014-09-19 23:57:06      阅读:516      评论:0      收藏:0      [点我收藏+]

标签:acm   c   算法   

Message Passing

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 1187    Accepted Submission(s): 423


Problem Description
There are n people numbered from 1 to n. Each people have a unique message. Some pairs of people can send messages directly to each other, and this relationship forms a structure of a tree. In one turn, exactly one person sends all messages s/he currently has to another person. What is the minimum number of turns needed so that everyone has all the messages?
This is not your task. Your task is: count the number of ways that minimizes the number of turns. Two ways are different if there exists some k such that in the k-th turn, the sender or receiver is different in the two ways.
 

Input
First line, number of test cases, T.
Following are T test cases.
For each test case, the first line is number of people, n. Following are n-1 lines. Each line contains two numbers.

Sum of all n <= 1000000.
 

Output
T lines, each line is answer to the corresponding test case. Since the answers may be very large, you should output them modulo 109+7.
 

Sample Input
2 2 1 2 3 1 2 2 3
 

Sample Output
2 6
 

Source
 

Recommend
zhuyuanchen520   |   We have carefully selected several similar problems for you:  5017 5016 5015 5014 5013 
 题意:
n个人,构成树形关系。每个人有一条独一无二的信息,每个人可以将自己的信息通过树边,共享给与他相邻的人,共享之后,被共享的人拥有他原有的信息和共享的来的信息。每次共享为一次操作,问每个人都拥有所有人的信息最小要的次数的共享方法有多少种。
思路:
容易的出,最短的时间内,当然是每个节点将自己的信息想外传出去一次,并且接受一次信息,也就是树边的2倍2*(n-1)。
然后可以证明,在最短时间内,所有的传递方式都有一个“信息转换点”——其他节点的信息首先传递到此节点,然后信息再从这个节点向其他节点传递。其实我们要求的就是拓扑序有多少种。定义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为根的答案就算出来了),以及其他一些辅助信息的值。然后按树的结构往下遍历,分别计算以其他节点为根的答案。以上是网上的思路。我想说的是自己的一点理解。为什么知道每个子树的拓扑序数目。就可以退出自己的拓扑序数目呢。其实很好理解的。当每个子树的拓扑序定下来之后。确定总顺序的时候。也就是要得到一个长度为cnt[u]拓扑序列。对于子树i。也有一个长度为cnt[i]拓扑序列,所以就要在cnt[u]里找cnt[i]个位置。其它子树再在剩下的子树里找。还有换根的时候该怎么推导。先写出
dp[u]‘=dp[u]*(n-sz[v]-1)!*sz[v]!/((n-1)!*dp[v])
dp[v]‘=dp[v]*(n-1)!*dp[u]‘/((sz[v]-1)!*(n-sz[v])!)。
带入dp[u]‘就可以约掉很多东西了。所以推公式的时候不要急着得到最后答案。还有就是为什么答案数就是拓扑序数的平方。因为信息传回去的时候就是你拓扑序嘛。和拓扑序数目一样的。每一个正拓扑序可以和一个逆拓扑序组合。所以就有平方种啦。
详细见代码:
#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1000010;
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long ll;
const ll mod=1e9+7;
ll fac[maxn],dp[maxn],ans;
int cnt,sz[maxn],n;
struct node
{
    int v;
    node *next;
} ed[maxn<<1],*head[maxn];
void adde(int u,int v)
{
    ed[cnt].v=v;
    ed[cnt].next=head[u];
    head[u]=&ed[cnt++];
}
ll pow_mod(ll x,int k)
{
    ll base=x,ret=1;
    while(k)
    {
        if(k&1)
            ret=(ret*base)%mod;
        base=(base*base)%mod;
        k>>=1;
    }
    return ret;
}
ll ni(ll x){    return pow_mod(x,mod-2);    }
void dfs(int fa,int u)
{
    ll tp;
    dp[u]=tp=sz[u]=1;
    for(node *p=head[u];p!=NULL;p=p->next)
    {
        int v=p->v;
        if(v==fa)
            continue;
        dfs(u,v);
        tp=(tp*ni(fac[sz[v]]))%mod;
        dp[u]=(dp[u]*dp[v])%mod;
        sz[u]+=sz[v];
    }
    dp[u]=(dp[u]*fac[sz[u]-1]%mod*tp)%mod;
}
void solve(int fa,int u,ll tp)
{
    ll tt;
    ans=(ans+tp*tp%mod)%mod;
    for(node *p=head[u];p!=NULL;p=p->next)
    {
        int v=p->v;
        if(v==fa)
            continue;
        tt=(tp*fac[n-sz[v]-1]%mod*fac[sz[v]])%mod;
        tt=(tt*ni(fac[sz[v]-1])%mod*ni(fac[n-sz[v]]))%mod;
        solve(u,v,tt);
    }
}
int main()
{
    int t,i,u,v,rt;

    fac[0]=fac[1]=1;
    for(i=2;i<maxn;i++)
        fac[i]=(i*fac[i-1])%mod;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        cnt=0,ans=0;
        for(i=1;i<=n;i++)
            head[i]=NULL;
        for(i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            adde(u,v);
            adde(v,u);
        }
        rt=(n+1)/2;
        dfs(-1,rt);
        solve(-1,rt,dp[rt]);
        printf("%I64d\n",ans);
    }
    return 0;
}



hdu 4661 Message Passing(树形DP&组合数学)

标签:acm   c   算法   

原文地址:http://blog.csdn.net/bossup/article/details/39404051

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!