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

相关文章: