【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; }