题意:有n个村庄,m条道路,要求寻找n个村庄的最小生成树,并且求出最小生成树中任意两点距离的期望
思路:最小生成树用模板生成,因为选中任意一对点的概率都是相同的,所以期望就是任意两点距离的和除以总情况数n*(n-1)/2
求任意两点间距离的和暴力循环O(n^2)的话会超时,可以用dfs,任意两点间距离的和其实是【最小生成树中所有边被访问次数*该边的权】的和。
要求被访问次数,可以先求以该边一节点为根节点的子树的节点总数num(包括这一节点,不包括该边的另一节点, 这里有点绕,参考下面例子),想要到达num个子节点就必须通过该边num次,同理该边的另一节点的子节点数一定是n-num,要通过改变n-num次,所以改变被通过次数时num*(n-num)次,推广到所有边,求和即可。
关于比较绕口的那句话举例:
在纸上画一棵树:
1 2
2 3
2 4
边1——2中:
以2为根节点的子树:
2 3
2 4
共有3个节点,要想达到这三个节点需要通过边1——2三次(对应点12、 13 、 14之间的距离)
以1位为根节点的子树只有1(这里一定是n-num次,仔细想一想),,所以只有一个节点,想要由其子节点到达边1——2只需要通过边1——2一次
所以边1——2一共被访问了1*3 = 3次
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 100000 + 10;
const int maxm = 1000000 + 10;
int n, m, par[maxn], num[maxn], vis[maxn];
double ans;
struct Edge {
int from, to, cost;
bool operator < (const Edge &e) {
return cost < e.cost;
}
}edge[maxm];
vector<P> g[maxn];
void init() {
for (int i = 0; i <= n; i++) {
par[i] = i;
g[i].clear();
}
memset(num, 0, sizeof(num));
memset(vis, 0, sizeof(vis));
ans = 0.0;
}
int seek(int x) { return par[x] == x ? x : par[x] = seek(par[x]); }
void dfs(int cur) {
num[cur] = 1;
vis[cur] = 1;
for (int i = 0; i < g[cur].size(); i++) {
int son = g[cur][i].first;
if (vis[son]) continue;
dfs(son);
num[cur] += num[son];
ans += 1.0 * num[son] * (n - num[son]) * g[cur][i].second;
}
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m);
init();
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &edge[i].from, &edge[i].to, &edge[i].cost);
}
sort(edge, edge + m);
ll sum = 0, cnt = 0;
for (int i = 0; i < m && cnt < n - 1; i++) {
int f = edge[i].from, t = edge[i].to, c = edge[i].cost;
int x = seek(f), y = seek(t);
if (x != y) {
par[x] = y;
sum += c;
cnt++;
g[f].push_back(make_pair(t, c));
g[t].push_back(make_pair(f, c));
}
}
dfs(1);
printf("%lld %.2lf\n", sum, ans/(1.0 * n * (n - 1) / 2));
}
return 0;
}