HDU
来源:互联网 发布:最好的网络电话软件 编辑:程序博客网 时间:2024/06/05 08:51
题意:有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;}