题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4661
题目大意:
有n个人,每个人知道一个独特的信息。
每一次操作,我们可以让一个人把自己知道的所有信息告诉另一个人。
要求经过最少次数的操作,可以让所有人知道所有信息。
求方案数。
算法:
显然最好的方法是所有人把消息先告诉同一个人,然后再由这个人把这个消息传给所有人。
这就构成了一个树形结构。
以某点为根,求出各点先收集自己子树的信息,再传递给父节点的方法数。
由于根节点收集信息的方法数等于把信息传递出去的方法数,所以取平方即可。
实际就是一棵树,以每个节点为根,得到的拓扑序列方法数的平方和。
不过我们要避免枚举根节点,否则会TLE。
有向树的拓扑方案数等于 n! 除以 以每个点为根的子树的大小的乘积。
所以就可以用一个很简单的树形DP来求出以各点为根的拓扑方案数。
具体可参见wzk巨巨的代码。
但是我想介绍一个比较麻烦的树形DP,因为这种处理的方式可能在别的地方用到。
要明白这个DP过程,首先要会求解一个问题:
(1)已知若干有序序列,现在把他们穿插组成一个新序列,使各原序列中的相对顺序不变。求方法数。
(2)已知上一个问题(1)的方法数,现在要把某个序列的元素全部从新序列中抽出来,问余下的序列有多少个不重复的。
比如序列1有1个数,序列2有2两个数,序列3有3个数。
问题(1)的方法数就是:C(1,1)*C(1+2,2)*C(1+2+3,3)
现在如果把序列2全部抽出来,
问题(2)的方法数就是:问题(1)的方法数/C(1+2+3,2)
证明方法比较简单,不解释了。
这道题实际要求两个方法数。
第一个是d1[u],代表从u的子树汇集到u的方法数。
第二个是d2[u],代表从u以外的节点汇集到u的方法数。
求解d1[u]的过程要从下向上更新。
假设u有若干棵子树: v1, v2, v3 ......
d1[u] = d1[v1] * C(size[v1], size[v1]) * d1[v2] * C(size[v1] + size[v2], size[v2]) * d1[v3] * C(size[v1] + size[v2] + size[v3], size[v3]) ......
看出来了么,就跟问题(1)是一样的。
对于各子树的每一种方法,它的内部已经是有序的。只要把它们穿插起来就可以了。
求解d2[u]的过程要从上向下更新
假设u的父节点是p[u].
那么信息从p[u]传递到点u时。
注意到此时u的兄弟节点的信息仍然要先汇集到p[u],再传递到点u。
p[u]的子树以外的节点信息,也要先汇集到p[u],再传递到点u。
如上图,我们来计算红色节点的d2[u]。
当信息从红色节点u的蓝色根节点p[u]传递到u时,
也就是树的根由p[u]换到u时,
点u(红色)的子节点(蓝色)仍然是它的子节点。
p[u](蓝色)原本是点u的父节点,现在变成了它的子节点。
p[u]除u以外的子节点(紫色)仍然是它的子节点。
点p[u]的父节点p[p[u]](紫色)也变成了它的子节点
调整后的关系图如下:
现在要求的d2[u],是原图中点u子树以外的点汇集到点u的方法数。
那么,d2[u] = (d2[p[u]] * C(n - size[u] - 1, n - size[p[u]])) * (d1[p[u]] / (d1[u] * C(size[p[u]] - 1, size[u])))
就是先把p[u]子树以外的点,也就是新图中以p[p[u]]为根的子树,作为p[u]的一棵子树插入。
方法数是d2[p[u]] * C(n - size[u] - 1, n - size[p[u]])。
新图中以p[u]的各子节点为根的子树大小之和是n - size[u] - 1,以p[p[u]]为根的子树大小是n - size[u]
然后要把以点u为根的子树从d1[p[u]]中去除,也就是d1[p[u]] / (d1[u] * C(size[p[u]] - 1, size[u]))
原图中以p[u]的各子节点为根的子树大小之和是size[p[u]] - 1,以u为根的子树大小是size[u]。
最后,所有点的d1[u] * d2[u]*C(n - 1, n - size[u])得到的乘积和就是答案。
要乘以C(n - 1, n - size[u])是因为把原图中u的子树和原图u子树以外的部分分别作为新图中u的子树,合并。
PS: 感谢xiaodao的解题报告为我提供了两种思路。
PPS:向叉姐学习了求逆元的一行写法,开心 >__<
PPPS:像我这种搞DP但是不搞数学的人。排列组合和数论可是一定要学好的哟 TwT
代码:
#pragma comment(linker,"/STACK:102400000,102400000")
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <climits>
#include <cmath>
#include <queue>
#include <vector>
#include <stack>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
const int MOD = 1000000007;
const int MAXN = 1000010;
vector<int> mm[MAXN];
int c[MAXN], d1[MAXN], d2[MAXN];
int size[MAXN];
int ans;
int n;
inline int fsqrt(int x)
{
return (long long)x * x % MOD;
}
int inv(int x)
{
return x <= 1 ? x : (long long)(MOD - MOD / x) * inv(MOD % x) % MOD;
}
inline int C(int n, int k)
{
return (long long)c[n] * inv(c[n - k]) % MOD * inv(c[k]) % MOD;
}
void dfs1(int u, int p)
{
d1[u] = 1;
size[u] = 0;
for (int i = 0; i < mm[u].size(); i ++)
{
int v = mm[u][i];
if (v == p)
{
continue;
}
dfs1(v, u);
size[u] += size[v];
d1[u] = (long long)d1[u] * d1[v] % MOD * C(size[u], size[v]) % MOD;
}
size[u] ++;
}
void dfs2(int u, int p)
{
ans = (ans + fsqrt((long long)d1[u] * d2[u] % MOD * C(n - 1, n - size[u]) % MOD)) % MOD;
for (int i = 0; i < mm[u].size(); i ++)
{
int v = mm[u][i];
if (v == p)
{
continue;
}
d2[v] = (long long)d1[u] * d2[u] % MOD * C(n - size[v] - 1, n - size[u]) % MOD;
d2[v] = (long long)d2[v] * inv(d1[v]) % MOD * inv(C(size[u] - 1, size[v])) % MOD;
dfs2(v, u);
}
}
void init()
{
c[0] = 1;
for (int i = 1; i < MAXN; i ++)
{
c[i] = (long long)c[i - 1] * i % MOD;
}
}
int main()
{
init();
int cas;
scanf("%d", &cas);
while (cas --)
{
ans = 0;
scanf("%d", &n);
for(int i = 0; i < n; i ++)
{
mm[i].clear();
}
for(int i = 1; i < n; i ++)
{
int u, v;
scanf("%d %d", &u, &v);
u --;
v --;
mm[u].push_back(v);
mm[v].push_back(u);
}
dfs1(0, -1);
d2[0] = 1;
dfs2(0, -1);
printf("%d\n", ans);
}
return 0;
}