BZOJ[2428][HAOI2006]均分数据 模拟退火

传送门ber~

模拟退火骗分

每次随机一个点,然后把它随机放入另外一个集合里,如果解更优就更新,如果不是更优,则根据当前温度随机判断是否更新,温度越高更新的几率越高,并不断降温,直到小于精度为止

多做几遍即可

代码如下:

#include
#include
#include
#include
#include
#define eps 1e-4
#define INF 2147483647
#define N 10020
using namespace std;
const int root=20020605;
inline int read(){
    int x=0,f=1;char c;
    do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
    do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
    return x*f;
}
int n,m;
int a[N],belong[N],sum[N];
double ans=1e30,ave;
inline double SA(){
    double ans=0,T=10000;
    memset(sum,0,sizeof sum);
    for(int i=1;i<=n;i++){
        belong[i]=rand()%m+1;///每个数随机放到一个集合里
        sum[belong[i]]+=a[i];
    }
    for(int i=1;i<=m;i++)
        ans+=(sum[i]-ave)*(sum[i]-ave);///ans是当前的每组的和与平均数差的平方和
    while(T>eps){
        T*=0.9;///降温
        int t=rand()%n+1,x=belong[t],y=0;///t:要拿走的数x:从哪个集合拿走y:放到哪里
        if(T>1000){///如果温度太高,则说明数列不稳定,挑一个和比较小的集合把t放到那里
            for(int i=1;i<=m;i++)
                if(!y || sum[i]else y=rand()%m+1;///否则随机找一个集合放进去
        if(x==y) continue;
        double tmp=ans;
        tmp-=(sum[x]-ave)*(sum[x]-ave);tmp-=(sum[y]-ave)*(sum[y]-ave);
        sum[x]-=a[t];sum[y]+=a[t];
        tmp+=(sum[x]-ave)*(sum[x]-ave);tmp+=(sum[y]-ave)*(sum[y]-ave);
        if(tmp<=ans || rand()%10000///如果解更优,或概率到那里的话就更新
            belong[t]=y,ans=tmp;
        else sum[x]+=a[t],sum[y]-=a[t];///否则不更新
    }
    return ans;
}
int main(){
    srand(root);
    n=read();m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        ave+=a[i];
    }
    ave=ave/(double)m;
    for(int i=1;i<=10000;i++)///多做几遍,玄学东西
        ans=min(SA(),ans);
    printf("%.2lf",sqrt(ans/m));
return 0;
}

你可能感兴趣的:(BZOJ,模拟退火,各省省选)