模拟赛上出了此题, 大概是本蒟蒻唯一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;
}