1. 程式人生 > 其它 >[2021.8集訓Day10/JZOJ.3410]【GDOI2014模擬】Tree

[2021.8集訓Day10/JZOJ.3410]【GDOI2014模擬】Tree

目錄
[2021.8集訓Day10/JZOJ.3410]【GDOI2014模擬】Tree

題目

思路

我們列舉一個參考平均數(注意不是真正的平均數),把每條邊按邊權與參考平均數的差的絕對值從小到大排序,跑一遍Kruskal,即可求出對應的標準差:

double now_average;//參考平均數

struct EDGE {
	int u , v , c;
}ed[M];
bool cmp(EDGE a , EDGE b) {
	return fabs((double)a.c - now_average) < fabs((double)b.c - now_average);
}

bool choosed[M];
double GetDeviation() {
	UF::init();
	std::sort(ed + 1 , ed + m + 1 , cmp);
	std::memset(choosed , 0 , sizeof(choosed));
	
	double average;
	for(int i = 1 ; i <= m ; i++) {
		int u = ed[i].u , v = ed[i].v;
		if(UF::findroot(u) == UF::findroot(v))
			continue;
		UF::uni(ed[i].u , ed[i].v);
		choosed[i] = true;
		average += ed[i].c;
	}
	
	double deviation = 0;
	average /= (double)(n - 1);//真正的平均數
	for(int i = 1 ; i <= m ; i++)
		if(choosed[i])
			deviation += fabs(average - (double)ed[i].c) * fabs(average - (double)ed[i].c);
	
	return sqrt(deviation / (double)(n - 1));
}

不難想到, 若\(x=\text{參考平均數}\),\(f(x)=參考平均數對應的標準差\),\(f(x)\)應該是一條比較絲滑的曲線,因此,我用了模擬退火.其實直接一個一個列舉也行,不過模擬退火跑得巨快300ms內就可以出結果.

程式碼

這不是GMOJ的AC程式碼

同樣的資料,本地A,洛谷雲IDE A , GMOJ WA(霧)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <cstring>
unsigned seed;

int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	seed *= re;
	return negt ? -re : re;
}

const int N = 110 , M = 2010;
struct EDGE {
	int u , v , c;
}ed[M];


const double delta_t = 0.993;
const double originT = 100;//初始溫度沒必要太大,因為c的範圍小

int n , m;
double ans_average , ans_deviation = 1e10;


namespace UF {
	int fa[N];
	void init() {
		for(int i = 1 ; i <= n ; i++)
			fa[i] = i;
	}
	int findroot(int x) {
		return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
	}
	inline void uni(int u , int v) {
		if(findroot(u) != findroot(v))
			fa[findroot(u)] = fa[v];
	}
}

double now_average = 50;
bool cmp(EDGE a , EDGE b) {
	return fabs((double)a.c - now_average) < fabs((double)b.c - now_average);
}
bool choosed[M];
int minc , maxc;
double GetDeviation() {
	UF::init();
	std::sort(ed + 1 , ed + m + 1 , cmp);
	std::memset(choosed , 0 , sizeof(choosed));
	
	double average;
	for(int i = 1 ; i <= m ; i++) {
		int u = ed[i].u , v = ed[i].v;
		if(UF::findroot(u) == UF::findroot(v))
			continue;
		UF::uni(ed[i].u , ed[i].v);
		choosed[i] = true;
		average += ed[i].c;
	}
	
	double deviation = 0;
	average /= (double)(n - 1);
	for(int i = 1 ; i <= m ; i++)
		if(choosed[i])
			deviation += fabs(average - (double)ed[i].c) * fabs(average - (double)ed[i].c);
	
	return sqrt(deviation / (double)(n - 1));
}
void simulate_anneal() {
	double average = ans_average;
	double t = originT;
	while(t > 1e-5) {//這個也沒必要太小,否則答案沒影響
		now_average = average + (rand() * 2 - RAND_MAX) * t;
		
		if(now_average < 0 || now_average > 100) {//優化下,溫度的範圍變小後,模擬退火可以跑得巨快.
			t *= delta_t;
			continue;
		}
		
		double now_deviation = GetDeviation();
		double DE = now_deviation - ans_deviation;
		if(DE < 0) {
			ans_average = average = now_average;
			ans_deviation = now_deviation;
		}
		else if(exp(-DE / t) * RAND_MAX > rand()) {
			average = now_average;
		}
		t *= delta_t;
	}
}
int main() {
	n = read() , m = read();
	for(int i = 1 ; i <= m ; i++) {
		ed[i].u = read() , ed[i].v = read() , ed[i].c = read();
	}
	
	std::srand(seed);
	
	for(int i = 1 ; i <= (m <= 20 ? 10 : 3) ; i++)
		simulate_anneal();
	std::printf("%.4f" , ans_deviation);
	return 0;
}