Hdu 4661 Message Passing(树形DP,扩展欧几里得)

今天多校的比赛题,在比赛最后2分钟AC了。。。太无语and惊险了!感觉被描述不清的题意坑了~


题意:

给你一颗树,每个节点都有各自独一无二的信息,每一次你可以把某个节点已有的所有信息传递给其相邻的另一个节点,最少需要多少次传递使得所有节点都有其他节点的所有信息?

不过。。这不是我们要解决的问题。。。现在要解决的是满足最少传递次数的所有的情况有多少种,结果对10^9+7取余。  ps:两种不同的情况为至少存在一个k,使得第k次传递信息接受方或者发送方不同。


坑爹的是我在比赛最后三分钟交了一发,-  -居然爆栈了,还是很淡定地手动加个栈交了,居然wrong answer了。。。这下无语了,还剩两分钟,这该怎么检查错误= =。。随便扫了一下代码,突然想到刚开始我对于n = 1的情况特判了下,直接输出0了,因为我认为既然只有一个人,那他就已经有所有的信息了,就不需要传递了。。。但这种时候还是顺便改成1又交了一发,其实根本没抱多少希望,居然AC了!!!这真是又雷又惊喜。。。


解题思路:

首先可以很容易得出最少传递次数为边数的两倍,这个不懂的自己在纸上画个图就清楚了。、

我们要使得所有信息传到所有节点,其实就是需要一个中介点,把这个店看成根节点,首先其所有子树把信息传上来,这样这个点就有了所有的信息了,然后把信息传回各个子树,这样所有点都会有所有的信息了。也就是说中介点可能会是任意一个点,那么我们就是要求出所有点为中介点的情况总数。


把问题先简化一下,我们令1为根节点,要求出所有子树把信息传到根节点的情况数,对于每一个子树内部传递顺序都是不受影响的,但是每个子树相对间的顺序是不一样的,这个就是组合数学了。用cnt[i]表示i节点所有子树的节点数,mul[i]表示该节点子树的信息汇聚到该节点的所有情况数,对于每一个节点都是把该节点所有子树的情况组合起来,mul[u] = mul[son1]*mul[son2]*...* C(cnt[u] , cnt[son1]+1) * C(cnt[u] - cnt[son1]-1 , cnt[son2]+1) *...

注意求组合数要用到除法取模,这里我直接用了扩展欧几里得来求逆,也可以利用费马小定理来解。

这样子只求出了以1为中介点的的情况数,其他节点保存的mul[u]只是表示其节点所有子树信息汇聚到该节点的情况数,而该节点父亲这边的信息还没有传过来,所以还要进行一次dfs进行转移,这个具体怎么转移大家可以好好想想。实在不懂的可以参考下我的代码。。。虽然写的有点搓,比赛最后时候有点急了。。。

ps:由于本人表达能力太差。。。写解题报告也是想为了提高表达能力,写的太烂希望大家别介意。。有什么不清楚的可以直接问我。。


code:

#pragma comment(linker,"/STACK:100000000,100000000")
#include 
#define LL __int64

const int mod = 1000000007;
const int maxn = 1000003;
struct EDGE{
    int to, next;
}edge[maxn*2];

int head[maxn], E, cnt[maxn];
LL jie[maxn], mul[maxn];

void init(int n) {
    for(int i = 1;i <= n; i++)  head[i] = -1;
    E = 0;
}

void newedge(int u, int to) {
    edge[E].to = to;
    edge[E].next = head[u];
    head[u] = E++;
}

LL modstyle(LL x) {  // 取余操作,由于取余%很慢的,这样写效率比较好
    if(x >= mod)    x %= mod;
    else if(x < 0) {
        x = (x%mod+mod)%mod;
    }
    return x;
}
//  扩展欧几里得  求逆元
LL exgcd(LL a ,LL b, LL &x, LL &y) {
    if(b == 0) {
        x = 1; y = 0;
        return a;;
    }
    LL ans = exgcd(b, a%b, y, x);
    y = y - a/b*x;
    return ans;
}
//  求组合数 C(n,k)
LL cal(int n, int k) {
    LL x, y;
    LL d = exgcd(jie[n-k]*jie[k], mod, x, y);
    x = modstyle(x);
    return jie[n]*x%mod;
}
//  第一次DFS统计出每个节点的所有的子树的总结点数cnt[u],子树节点信息传到该节点的情况数mul[u]
void dfs1(int u, int pre) {
    cnt[u] = 0;
    mul[u] = 1;
    for(int i = head[u];i != -1;i = edge[i].next) {
        int to = edge[i].to;
        if(to != pre) {
            dfs1(to, u);
            cnt[u] += cnt[to]+1;
            mul[u] = modstyle(mul[u]*mul[to]);
        }
    }
    int sum = cnt[u];
    for(int i = head[u];i != -1;i = edge[i].next) {
        int to = edge[i].to;
        if(to != pre) {
            mul[u] = modstyle(mul[u]*cal(sum, cnt[to]+1)); //   对于子树的情况利用组合数进行汇聚
            sum -= cnt[to]+1;
        }
    }
}

LL ans = 0;
int n;

void dfs2(int u, int pre) {
    if(pre != -1) {  //   父节点的信息传到该节点进行更新计算
        LL x, y;
        LL d = exgcd(cal(n-1, cnt[u]+1), mod, x, y);
        x = modstyle(x);
        LL cur = mul[pre]*x%mod;
        cur = cur*cal(n-1, cnt[u])%mod;
        mul[u] = cur;
        ans = (ans + cur*cur)%mod;
    }
    for(int i = head[u];i != -1;i = edge[i].next) {
        int to = edge[i].to;
        if(to != pre) {
            dfs2(to, u);
        }
    }
}

int main() {
    int i, t, u, to;
    jie[0] = 1;
    for(i = 1;i <= 1000000; i++) {
        jie[i] = modstyle(jie[i-1]*i);
    }
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        if(n == 1) {
            puts("1");
            continue;
        }
        init(n);
        for(i = 0;i < n-1; i++) {
            scanf("%d%d", &u, &to);
            newedge(u, to);
            newedge(to, u);
        }
        dfs1(1, -1);
        ans = mul[1]*mul[1]%mod;
        dfs2(1, -1);
        printf("%I64d\n", ans);
    }
    return 0;
}



你可能感兴趣的:(ACM_HDU,ACM_树形DP)