HDU 4661 Message Passing ( 树DP + 推公式 )

参考了:

http://www.cnblogs.com/zhsl/archive/2013/08/10/3250755.html

http://blog.csdn.net/chaobaimingtian/article/details/9852761

题意:一个有n个节点的树,每个节点存有一份独一无二的信息,要求用最小的步数,把每个节点的信息共享给所有的节点。一个节点把自己所包含的所有信息传递给相邻的一个节点为一步。

题目不是求最小的步数,而是问最小的步数下,信息传递的方法有多少种。

分析:

  1. 最小步数:所有节点把信息传给同一个节点,再由这个节点传给其他节点。因此最小步数为树边数的2倍,即2*(n-1)。我们把这个节点称为信息交换的中心节点。
  2. 拓扑排序数:从根节点开始,沿着每个边依次遍历每个点的不同走法。比如对于树:n=4,边为(1,2)(1,3)(2,4),边的编号依次为1,2,3,设根为1,那么不同的遍历方案有(这里写的是边的序号):(1,3,2)(1,2,3)(2,1,3)三种。我们说以1为根的拓扑排序数为3。
  3. 假设所有节点把信息传给中心节点的拓扑排序数为X,那么再由这个节点传给其他节点的拓扑排序数也为X,总的方法数就是X2
  4. 枚举所有的中心节点Xi, 总方法数即为ans = sum( Xi2 ), 1 <= i<= N;
  5. 求每个节点的拓扑排序数:DFS一次,记录dp[u], cnt[u]。dp[u]为以u为根节点的子树的拓扑排序数,cnt[u]为以u为根节点的子树的节点的个数。假设v1,v2为u的两个子树,那么v1, v2合并后的拓扑排序数为:sum = dp[v1]*dp[v2]*C( cnt[v1]+cnt[v2], cnt[v1]);(C为组合数公式)对于u的所有儿子,可以采用两两合并的方法。
  6. 求以u为中心节点的拓扑排序数dp[u](即u为整棵树的根节点):再次DFS一遍。

设u的父亲为fa,因为DFS是先根序遍历,因此我们在求以u为中心节点的拓扑排序数dp[u]之前,已经先将以fa为中心节点的拓扑排序数dp[fa]求了出来,因此下面我们可以直接使用这个值。

我们将fa中除去子树u的所有子树合并成一个子树t,根据上面的式子:

 

#pragma comment(linker,"/STACK:102400000,102400000")
#include <cstdio>
#include <cstdlib>
#include <cstring>

#define LL long long int

using namespace std;

const int MAXN = 1000010;
const LL MOD = 1000000007;

struct Edge
{
    int v;
    int next;
};

int head[MAXN];
Edge D[ MAXN << 1 ];
int N, ans;
int EdgeN;
LL cnt[MAXN];
LL dp[MAXN];
LL fac[MAXN];
LL rev[MAXN];

void AddEdge( int u, int v )
{
    D[EdgeN].v = v;
    D[EdgeN].next = head[u];
    head[u] = EdgeN++;
    return;
}

//求逆元模板
void ExGcd( LL a, LL b, LL& d, LL& x, LL& y )
{
    if ( !b ) { d = a, x = 1, y = 0; }
    else
    {
        ExGcd( b, a % b, d, y, x );
        y -= x * ( a / b );
    }
    return;
}

LL GetInverse( LL num )
{
    LL d, x, y;
    ExGcd( num, MOD, d, x, y );
    return ( x % MOD + MOD ) % MOD;
}

//预处理出所有阶乘和逆元
void init()
{
    fac[0] = 1;
    for ( int i = 1; i < MAXN; ++i )
        fac[i] = ( fac[i - 1] * i ) % MOD;

    for ( int i = 1; i < MAXN; ++i )
        rev[i] = GetInverse( fac[i] );

    return;
}

//第一次DFS,求出以cur为根的子树的节点个数cnt[u]和拓扑排序数dp[cur]
void DFS1( int cur, int fa )
{
    cnt[cur] = dp[cur] = 1;
    for ( int i = head[cur]; i != -1; i = D[i].next )
    {
        if ( D[i].v == fa ) continue;
        DFS1( D[i].v, cur );
        cnt[cur] += cnt[ D[i].v ];
        dp[cur] = ( (dp[cur]*dp[ D[i].v ])%MOD * rev[cnt[D[i].v]] )%MOD;
    }
    dp[cur] = ( dp[cur] * fac[ cnt[cur]-1 ] ) % MOD;
    return;
}

//第二次DFS,求出以cur为中心的拓扑排序数dp[cur]
void DFS2( int cur, int fa )
{
    if ( cur != 1 )
    {
        dp[cur] = (( (dp[fa]*cnt[cur])%MOD )*GetInverse(N-cnt[cur]))%MOD;
        ans = (ans + dp[cur]*dp[cur]%MOD)%MOD;
    }
    for ( int i = head[cur]; i != -1; i = D[i].next )
    {
        if ( D[i].v == fa ) continue;
        DFS2( D[i].v, cur );
    }
    return;
}

int main()
{
    init();
    int T;
    scanf( "%d", &T );
    while ( T-- )
    {
        scanf( "%d", &N );
        EdgeN = 0;
        memset( head, -1, sizeof(int)*(N+4) );
        for ( int i = 1; i < N; ++i )
        {
            int u, v;
            scanf( "%d%d", &u, &v );
            AddEdge( u, v );
            AddEdge( v, u );
        }

        DFS1( 1, -1 );
        ans = dp[1] * dp[1] % MOD;
        DFS2( 1, -1 );

        printf("%I64d
", ( ans + MOD ) % MOD );
    }
    return 0;
}
原文地址:https://www.cnblogs.com/GBRgbr/p/3312866.html