1. 程式人生 > >[luogu2503][HAOI2006]均分數據

[luogu2503][HAOI2006]均分數據

如果 sizeof sqrt 均方差 最小 種子 put haoi2006 main

題目描述

已知N個正整數:A1、A2、……、An 。今要將它們分成M組,使得各組數據的數值和最平均,即各組的均方差最小。均方差公式如下:
技術分享圖片

分析

萬物皆可頹火,我們首先將初始的答案當做一半一半的答案,然後我們隨機化抽取兩個部分的數據。根據題目中的描述,因為兩個組別之間數據個數只能是差一,那麽差不多就是一半一半的情況。那麽我們就只需要分塊兩部分,然後隨機交換,如果兩個數據交換之後能使答案能更優,那麽就交換,如果不能讓我們的答案變得更加優,那麽就讓隨機概率,這個概率很明顯是越到後面交換的概率越小,那麽我們就是exp(delta) < t * Rand(),那麽就交換,否則就不交換。
模擬退火的精髓還是這個調參,這道題目我一遍A掉了,感覺有一點歐皇。

我給出一個比較優秀的隨機種子,是ouhuang和6666666的取模,就是15346301。

ac代碼

#include <bits/stdc++.h>
#define ms(a,b) memset(a, b, sizeof(a))
#define db double 
using namespace std;
inline char gc() {
    static char buf[1 << 16], *S, *T;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, 1 << 16, stdin);
        if (T == S) return EOF;
    }
    return *S ++;
}
template <typename T>
inline void read(T &x) {
    T w = 1;
    x = 0;
    char ch = gc();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = gc();
    }
    while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gc();
    x = x * w;
}
template <typename T>
void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
#define N 305
db ans = 1e30, ave = 0;
int sum[N], pos[N], a[N];
int n, m;
void SA(db T){
    ms(sum, 0);
    for (int i = 1; i <= n; i ++) {
        pos[i] = rand() % m + 1;
        sum[pos[i]] += a[i];
    }
    db res = 0;
    for (int i = 1; i <= m; i ++) 
        res += (1.0 * sum[i] - ave) * (1.0 * sum[i] - ave);
    while (T > 1e-4) {
        int t = rand() % n + 1, x = pos[t], y;
        if (T > 500) y = min_element(sum + 1, sum + 1 + m) - sum;
        else y = rand() % m + 1;
        if (x == y) continue;
        db tmp = res;
        res -= (sum[x] - ave) * (sum[x] - ave);
        res -= (sum[y] - ave) * (sum[y] - ave);
        sum[x] -= a[t], sum[y] += a[t];
        res += (sum[x] - ave) * (sum[x] - ave);
        res += (sum[y] - ave) * (sum[y] - ave);
        if (res < tmp || rand() % 10000 <= T) pos[t] = y;
        else sum[x] += a[t], sum[y] -= a[t], res = tmp;
        ans = min(ans, res);
        T *= 0.98;
    }
}
int main() {
    srand(20040127);
    read(n); read(m);
    for (int i = 1; i <= n; i ++) {
        read(a[i]);
        ave += 1.0 * a[i];
    }
    ave /= 1.0 * m;
    for (int i = 1; i <= 1500; i ++) SA(10000);
    printf("%.2lf\n", sqrt(ans / m));
    return 0;
}

[luogu2503][HAOI2006]均分數據