【bzoj3754】Tree之最小方差树 最小生成树

题目描述

给出一张无向图,求它的一棵生成树,使得选出的所有边的方差最小。输出这个最小方差。

输入

第一行两个正整数N,M
接下来M行,每行三个正整数Ui,Vi,Ci
N<=100,M<=2000,Ci<=100

输出

输出最小的标准差,保留四位小数。

样例输入

3 3
1 2 1
2 3 2
3 1 3

样例输出

0.5000


题解

最小生成树

由于Ci很小,因此选出边的总和不会很大。可以考虑枚举这个总和(即平均值)。

然后把每条边的边权看作 $|c_i-\bar c|$ ,跑最小生成树,记录选出的边,然后根据实际选择计算实际方差(注意这里算平均值用的不是枚举的总和,而是实际计算的平均值)

简单证明一下这样为什么是对的:当枚举的总和是最终答案的总和时,选出的边一定是最终答案,而每次计算的答案不会出现错误情况,因此它们的最小值一定是答案。

细节处理中,可以先对所有边按照权值排序,在枚举总和(平均值)时,记录第一个大于等于这个平均值的边的位置 $p$ 。这样,这个位置 $p$ 左边的代价就是 $\bar c-c_i$ ,右边的代价就是 $c_i-\bar c$ ,使用二路归并的方法依次取最小值即可。这个方法可以避免每次都对所有边排序。

时间复杂度 $O(m\log m+cnm)$

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
struct data
{
	int x , y , z;
	bool operator<(const data &a)const {return z < a.z;}
}a[2010];
bool v[2010];
int f[110];
int find(int x)
{
	return x == f[x] ? f[x] : f[x] = find(f[x]);
}
int main()
{
	int n , m , i , j , k , now , p = 1;
	double s , t , ans = 1 << 30;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &a[i].x , &a[i].y , &a[i].z);
	sort(a + 1 , a + m + 1);
	for(i = 0 ; i <= (n - 1) * 100 ; i ++ )
	{
		while(p <= m && a[p].z * (n - 1) < i) p ++ ;
		for(j = 1 ; j <= n ; j ++ ) f[j] = j;
		for(j = 1 ; j <= m ; j ++ ) v[j] = 0;
		j = p - 1 , k = p , s = t = 0;
		while(j || k <= m)
		{
			if(k > m || (j && i - a[j].z * (n - 1) < a[k].z * (n - 1) - i)) now = j -- ;
			else now = k ++ ;
			if(find(a[now].x) != find(a[now].y))
				f[f[a[now].x]] = f[a[now].y] , s += a[now].z , v[now] = 1;
		}
		s /= (n - 1);
		for(j = 1 ; j <= m ; j ++ )
			if(v[j])
				t += (a[j].z - s) * (a[j].z - s);
		ans = min(ans , t);
	}
	printf("%.4lf\n" , sqrt(ans / (n - 1)));
	return 0;
}

 

posted @ 2017-12-08 17:01  GXZlegend  阅读(1094)  评论(0编辑  收藏  举报