bzoj 2428: [HAOI2006]均分数据 (模拟退火)

题目描述

传送门

题目大意:给出n个数,分成m组,设每组的和是xi
mi=1(xix¯)m ,其中 x¯=mi=1xim

题解

暴力所有的方案数时间复杂度应该是 O(mn)
我们考虑用模拟退火来做。刚开始先随便分成m组,然后每次随机一个组,从中随机揪出一个元素扔给组内和最小的组。如果当前答案比之前的答案更优,就转移到当前状态,否则以一定的概率转移到当前状态。
做的过程中一直对所有的答案取min。
因为是随机化算法,所以有一定的概率出错,所以我们在时间允许的情况下多做几次,降低出错的概率。

代码

#include
#include
#include
#include
#include
#include
#define N 23
#define eps  1e-7
#define T 100
#define delta 0.98
using namespace std;
double a[N],sum[N],mn;
int pos[N],n,m,q[N];
double pow(double x){
    return x*x;
}
double getans()
{
    double bx=0,ans=0;
    for (int i=1;i<=m;i++) bx+=sum[i]; bx/=(1.0*m);
    for (int i=1;i<=m;i++) ans+=pow(sum[i]-bx);
    return ans;
}
int getmin()
{
    int x=1;
    for (int i=2;i<=m;i++)
     if (sum[x]>sum[i]) x=i;
    return x;
}
void solve()
{
    double t=T; double ans=getans(); 
    mn=min(mn,ans);
    while (t>eps) {
        double ans1=0;
        int x=rand()%m+1; int y=getmin(); 
        if  (y==x) {
            if (x==m) x--;
            else x++;
        }
        int cnt=0;
        for (int i=1;i<=n;i++)
         if(pos[i]==x) q[++cnt]=i;
        int k=rand()%cnt+1;
        sum[x]-=a[q[k]]; sum[y]+=a[q[k]]; pos[q[k]]=y; 
        ans1=getans(); bool pd=false;
        if (ans1>ans) 
         if (rand()%1000>t) {
            sum[x]+=a[q[k]]; sum[y]-=a[q[k]]; pos[q[k]]=x;
            pd=true;
         }
        if (!pd) ans=ans1;
        mn=min(mn,ans1);
        t*=delta;
    }
}
int main()
{
    freopen("a.in","r",stdin);
//  freopen("data.out","w",stdout);
    scanf("%d%d",&n,&m); mn=1000000000;
    for (int i=1;i<=n;i++) scanf("%lf",&a[i]);
    for (int i=1;i<=m;i++) sum[i]+=a[i],pos[i]=i;
    for (int i=m+1;i<=n;i++) sum[m]+=a[i],pos[i]=m;
    for (int i=1;i<=1000;i++) 
     solve();
    printf("%.2lf\n",sqrt(mn/(1.0*m)));
}

你可能感兴趣的:(模拟退火)