CF437D The Child and Zoo
模拟赛上出了此题, 大概是本蒟蒻唯一ac的题了
考场上第一眼康到这道题时, 因为只涉及路径上最小值最大的问题, 所以想到了货车运输, 跑出它的最大生成树,由最大生成树的性质可知, f(p, q) 必定在新建的树上的路径上
题目中让求所有点对的f值之和, 好像不太好求, 所以我们分别考虑每条边对答案的贡献。 先将树上的边按边权从大到小排序,使用并查集维护联通块的大小, 一个一个加边, 它对答案的贡献就是两端点联通块大小的乘积再乘上新边边权.
何哉? 因为边权是从大到小排序的, 所以两联通块中的边权值均大于新边。 它的权值是最小的, 所以左端点所在联通块的点集到右端点所在联通块的点集的f值均为新边的边权。
最后, 发现最大生成树的过程和算答案的过程可以合并,详情见代码,还是很好理解的
talk is cheap, show me the code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<iomanip>
#include<algorithm>
using namespace std;
template <typename T>
void read(T &x) {
x = 0; int f = 1;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') f = -1;
c = getchar();
}
while (isdigit(c)) {
x = (x << 3) + (x << 1) + c - '0';
c = getchar();
}
x *= f;
}
const int N = 100500;
const int M = 1005000;
struct node{
int x, y;
int w;
bool operator < (const node &i) const {
return w > i.w;
}
}e[M];
long long n, m;
int f[N], a[N];
long long ans = 0;
int siz[N];
int find(int x) {
if (x == f[x]) return x;
return f[x] = find(f[x]);
}
int main() {
read(n), read(m);
for (int i = 1;i <= n; i++) f[i] = i, siz[i] = 1;
for (int i = 1;i <= n; i++) read(a[i]);
for (int i = 1;i <= m; i++) {
int x, y;
read(x), read(y);
e[i] = (node){x, y, min(a[x], a[y])};
}
sort(e + 1,e + m + 1);
for (int i = 1;i <= m; i++) {
int fx = find(e[i].x), fy = find(e[i].y);
if (fx == fy) continue;
f[fx] = fy;
ans += (long long)siz[fx] * siz[fy] * e[i].w;
siz[fy] += siz[fx];
}
cout << setprecision(6) << (long double) ans * 2 / (long double)(n * (n-1)) << endl;
return 0;
}