[HAOI2006]均分数据

题意

Here

思考

今天练习的第二道模拟退火……
\(WA\) 了好几次发现是最后没有开根号!!

参考了一下 \(attack\) 的题解,主要思路是先随机分组,然后随机选一个数分到权值最小的组里来退火。(ps:玄学质数好用)

代码

#include<bits/stdc++.h>
using namespace std;
typedef double D;
const int N = 110;
int a[N], pos[N], cnt[N], n, m;
D sum[N], ans = 1e18, x, SUM, ANS = 1e18;
void SA(){
    memset(sum, 0, sizeof(sum)); ANS = 0;
    for(int i=1; i<=n; i++) pos[i] = rand() % m + 1, sum[pos[i]] += a[i];
    for(int i=1; i<=m; i++) ANS += (sum[i] - x) * (sum[i] - x);
    D t = 10000; int p, i;
    while(t > 1e-14){
        p = min_element(sum + 1, sum + m + 1) - sum;
        i = rand() % n + 1;
        D PANS = ANS;
        ANS -= (sum[ pos[i] ] - x) * (sum[ pos[i] ] - x) + (sum[p] - x) * (sum[p] - x);
        sum[ pos[i] ] -= a[i]; sum[p] += a[i];
        ANS += (sum[ pos[i] ] - x) * (sum[ pos[i] ] - x) + (sum[p] - x) * (sum[p] - x);
        if( (PANS > ANS) || (exp( (D)(ANS - PANS) / t) < ((D)rand() / (D)RAND_MAX)) ){
            pos[i] = p;
        }
        else{
            sum[p] -= a[i]; sum[pos[i]] += a[i]; ANS = PANS;
        }
        t *= 0.99;
    }
    if(ANS < ans) ans = ANS;
}
int main(){
    srand(time(0)); srand(19260817); srand(rand());
    cin >> n >> m;
    for(int i=1; i<=n; i++){
        cin >> a[i]; x += a[i];
    }
    x /= m;
    int cnt = 1926;
    while(cnt--){
        SA();
    }
    cout << fixed << setprecision(2) << sqrt(ans / (D)m);
    return 0;
}

总结

感觉这种随机化算法很玄学,考试的话还是建议先打完暴力分,如果实在没思路再考虑模拟退火,不过算法本身局限性也很大,适用于数据范围小一些的题,部分计算几何也可做。

posted @ 2018-11-06 19:31  alecli  阅读(152)  评论(0编辑  收藏  举报