1. 程式人生 > 其它 >[DarkBZOJ3754] Tree之最小方差樹

[DarkBZOJ3754] Tree之最小方差樹

暴力真的能出奇跡!

前言

感覺如果不講做法應該要想上好一會,甚至可能做不出來。

題目

DarkBZOJ

講解

拿到這道題很有可能沒什麼思路,我們就從暴力入手。

先把標準差的公式寫出來:

\[\sqrt{\frac{\sum_{0\le i<n}(a_i-\overline{a})^2}{n}} \]

對於這道題,顯然我們知道生成樹是 \(n-1\) 條邊,而且外面的根號其實不影響相對大小,所以我們去掉這些東西( \(a_i\) 為邊權):

\[\sum (a_i-\overline{a})^2 \]

顯然我們如果知道了 \(\overline{a}\) 的大小,就可以通過最小生成樹演算法求出上面這個東西的最小值,可惜我們不知道。

但我們真的不能知道嗎?

寫出它的表示式:\(\overline{a}=\frac{\sum a_i}{n-1}\) ,顯然 \(\overline{a}\)\(\sum a_i\) 決定,我們發現 \(\sum a_i\) 最多不過 \(10^4\),直接列舉即可。

但是這還不夠,如果你列舉一次做一次 \(\tt Kruskal\),發現複雜度達到了 \(O(cnm\log_2m)\),是有問題的。

其實我們只需要一次排序,列舉一次的時候用雙指標維護一下當前情況下權值 \((a_i-\overline{a})\) 最小,且原始權值 \(a_i\) 小於和大於平均值的邊,並不難實現。

於是複雜度就是 \(O(m\log_2m+cnm)\)

我真不想說髒話。(攤手

程式碼

不懂戳我
//12252024832524
#include <cmath>
#include <cstdio>
#include <cstring> 
#include <algorithm>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 105;
const int MAXM = 2005;
const int INF = 0x3f3f3f3f;
int n,m;
double ans = INF;

LL Read()
{
	LL x = 0,f = 1; char c = getchar();
	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1) 
{
	if(x < 0) x = -x,putchar('-');
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

struct edge
{
	int u,v,w;
	bool operator < (const edge &px)const{
		return w < px.w;
	}
}e[MAXM];

int f[MAXN];
int findSet(int x){if(x^f[x])f[x]=findSet(f[x]);return f[x];}
void unionSet(int u,int v){f[findSet(u)] = findSet(v);}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1;i <= m;++ i) e[i].u = Read(),e[i].v = Read(),e[i].w = Read();
	sort(e+1,e+m+1);
	e[0].w = -INF; e[m+1].w = INF;
	for(int C = 1;C <= 100*(n-1);++ C)
	{
		for(int i = 1;i <= n;++ i) f[i] = i;
		int cnt = n-1,s = 0; double av = C / (n-1.0),cur = 0;
		int r = upper_bound(e+1,e+m+2,edge{0,0,(int)ceil(av)}) - e;
		int l = r-1;
		while(cnt)
		{
			while(l > 0 && findSet(e[l].u) == findSet(e[l].v)) --l;
			while(r <= m && findSet(e[r].u) == findSet(e[r].v)) ++r;
			double L = av-e[l].w,R = e[r].w-av;
			if(L < R) cur += L*L,s += e[l].w,unionSet(e[l].u,e[l].v),--l;
			else cur += R*R,s += e[r].w,unionSet(e[r].u,e[r].v),++r;
			--cnt;
		}
		if(s == C) ans = Min(ans,sqrt(cur/(n-1)));
	}
	printf("%.4f",ans);
	return 0;
}