HDU 6446 Tree and Permutation
题目
给一颗 n 个节点的带权树树,定义一个排列的距离为按排列顺序最短路径和。求 n 的全排列的距离和。
例如: n = 4,其中一个排列为 4 1 2 3。那么这个排列距离为 4 -> 1 -> 2 -> 3。x ->y 指的是 x 到 y 的最短路径。
分析
对于全排列,分析任意两点距离对排列的贡献。假如 n = 4:
1 2 x x
x 1 2 x
x x 1 2
那么 1 到 2 最短距离 d12,一共出现的次数为 3 * A(2, 2) = 3 * 2!,(A 为组合数,x 位置可以随便放)。2 到 1 的同理。
因此 1,2 两点之间最短距离在排列中出现总次数为 d i s = 2 ∗ ( n − 1 ) ∗ ( n − 2 ) ! = 2 ∗ ( n − 1 ) ! dis = 2 * (n-1) * (n-2)! = 2 * (n-1)! dis=2∗(n−1)∗(n−2)!=2∗(n−1)!。
设 D D D 代表任意两点之间最短距离和,那么最终答案就是 a n s = D ∗ d i s ans = D * dis ans=D∗dis
现在问题转化成为求树上任意两点之间距离和。对于树上最短距离当然可以用 LCA + DFS来求,不过这种枚举两个节点的复杂度为 n 2 n^2 n2,不能接受。。。
还是考虑贡献,接下来考虑每条边权的贡献。对于每条边,在答案中出现的次数就是求距离的时候经过它的总次数。也就是这条边两边节点总数的乘积。
对于树来说,我们用 DFS 遍历完一个子树就可以获得子树的节点和 cnt,另一边的就是 n-cnt。子树的父边就可以计算出来了。(要存双向边,开两倍空间) c n t ∗ ( n − c n t ) ∗ l e n cnt * (n-cnt) * len cnt∗(n−cnt)∗len
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define fuck(x) cout<<x<<endl
const int N = 2e5 + 10; // 存正反向两次边
const ll mod = 1e9 + 7;
int n;
int h[N], w[N], to[N], ne[N], idx;
ll fac[N], ans;
void init(){
fac[0] = 1;
for(int i = 1; i < N; i++){
fac[i] = fac[i - 1] * i % mod;
}
}
void add(int u, int v, int z){
to[idx] = v, w[idx] = z, ne[idx] = h[u], h[u] = idx++;
}
int dfs(int rt, int from, int len){
ll cnt = 1;
for (int i = h[rt]; ~i; i = ne[i]){
int v = to[i];
if(v != from)
cnt += dfs(v, rt, w[i]);
}
ans = (ans + (cnt * (n - cnt) % mod * len % mod)) % mod;
return cnt;
}
int main(){
init();
while(~scanf("%d", &n)){
memset(h, -1, sizeof(h));
idx = 0;
for(int i = 0, x, y, z; i < n-1; i++){
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
ans = 0;
dfs(1, 0, 0);
printf("%lld\n", ans * 2 % mod * fac[n - 1] % mod);
}
return 0;
}
/*
*/