[2021.8集訓Day10/JZOJ.3410]【GDOI2014模擬】Tree
阿新 • • 發佈:2021-08-20
目錄
[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; }