2428: [HAOI2006]均分数据

模拟退火。
一种十分玄学的随机算法,网上可以查到比较详细的资料。

先随机地把数分成m组,每次随机地选择一个数,一开始直接选最小的一组,后来就随机一组,把这个数换到该组看看答案能不能变小,如果变小则换,如果没有变小,按模拟退火的玄学方式判断一下,也要交换。

srand(time(0))在bzoj会RE,不知为何。

非常玄学的正确性,没拍多少数据就会出现差别,然而OJ上可以过的。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<ctime>
#define Ti 1e4
const int maxn=30;
using namespace std;
int n,from[maxn],m;
double tp,ave,ans,p[maxn],sum[maxn];
double pf(double x) {return x*x;}
double solve() {
    double res=0;
    for(int i=1;i<=m;i++) sum[i]=0;
    for(int i=1;i<=n;i++) {
        from[i]=rand()%m+1;
        sum[from[i]]+=p[i];
    }
    for(int i=1;i<=m;i++) 
        res+=pf(sum[i]-ave);
    double T=Ti;
    while(T>0.1) {
        int x=rand()%n+1,y;
        y=T>1000?min_element(sum+1,sum+m+1)-sum:rand()%m+1;
        tp=res-pf(sum[y]-ave)+pf(sum[y]+p[x]-ave)-pf(sum[from[x]]-ave)+pf(sum[from[x]]-p[x]-ave);
        if(tp<res||exp((res-tp)*Ti/T)>(double)rand()/RAND_MAX) {
            res=tp;
            sum[y]+=p[x];
            sum[from[x]]-=p[x];
            from[x]=y;
        } 
        T*=0.9;
    }
    return res;
}

int main()
{
    srand(159159141);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%lf",&p[i]);
        ave+=p[i];
    }
    ave/=m;
    ans=solve();
    for(int i=1;i<=Ti;i++) 
        ans=min(ans,solve());
    printf("%.2lf
",sqrt(ans/m));
    return 0;
}
View Code

 这篇博客写得十分好

其实不懂能不能随便放别人博客,侵删

原文地址:https://www.cnblogs.com/Achenchen/p/7506554.html