题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=6446
题目:
给出一颗树,n个结点,n个结点有n!种排列组合,假设其中一种是1 3 5 2 4,那么就要求出1-3,3-5,5-2,2-4的路径总和,问所有排列组合的路径总和。
思路:
n的全排列,每组排列都会有n -1条边,由于是全排列,所以可以发现每条边出现的次数都是一样的。
而边的类别总共有 n * (n - 1) / 2; 为什么除以2,因为我们将 3 - 2 和 2- 3视为同一条边,而 n * (n - 1) 中将 3-2 , 2- 3视为了不同的边。
n的全排列的总共的边数为 n! * (n - 1)
所以每种边出现了 n! * (n - 1) / (n * ( n - 1 ) / 2) == 2 * (n - 1)!
于是,我们的思路就想到了,求每条边的长度,求其总和为v, 然后 v * 2 * (n - 1)!
其中,由于有多组测试数据,所以我们可以先将 (1~1e5) ! 预处理出来。
而求每天边的长度,
边的两个端点所对应的节点数的乘积,就是这条边出现的次数。然后 * 2 就是A-B,B-A这样的总出现的次数。
所以,可以使用树型dp中的dfs的方式进行求解边出现的次数。
以某个节点为根节点,
然后通过dfs遍历,
将其子节点作为根节点的节点总数不断的求出来,w[i]为其边的值, w[i] 乘以 对应的子节点总数 t * (n - t) * 2。就是这条边出现的总长度。
代码实现:
# include <iostream>
# include <cstring>
using namespace std;
const int N = 1e5 + 10,M = 2 * N, mod = 1e9 + 7;
int p[N];
int h[N],e[M],ne[M],w[M],idx;
int n;
void add(int a ,int b , int wei)
{
e[idx] = b;
w[idx] = wei;
ne[idx] = h[a];
h[a] = idx++;
}
long long ans = 0;
int dfs(int u , int father)
{
int total = 1;
for(int i = h[u] ; i != -1 ; i = ne[i])
{
int j = e[i];
int len = w[i];
if(j == father)
{
continue;
}
int t = dfs(j,u);
ans = (ans + (long long)t * (n - t) * len) % mod;
total += t;
}
return total;
}
int main()
{
p[1] = 1;
for(int i = 2 ; i < 1e5 + 10 ; i++)
{
p[i] = (long long)p[i - 1] * i % mod;
}
while(~scanf("%d",&n))
{
memset(h,-1,sizeof h);
idx = 0;
ans = 0;
for(int i = 1 ; i <= n - 1 ; i++)
{
int a,b,w;
scanf("%d %d %d",&a,&b,&w);
add(a,b,w);
add(b,a,w);
}
dfs(1,-1);
printf("%d\n",((long long)ans * 2 * p[n - 1]) % mod);
}
return 0;
}