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

题目链接

bzoj2428

题目描述

Description
已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:这里写图片描述
,其中σ为均方差,这里写图片描述是各组数据和的平均值,xi为第i组数据的数值和。

Input

第一行是两个整数,表示N,M的值(N是整数个数,M是要分成的组数)
第二行有N个整数,表示A1、A2、……、An。整数的范围是1–50。
(同一行的整数间用空格分开)

Output

这一行只包含一个数,表示最小均方差的值(保留小数点后两位数字)。

Sample Input

6 3
1 2 3 4 5 6

Sample Output

0.00

HINT

对于全部的数据,保证有K<=N <= 20,2<=K<=6

题解

模拟退火的过程也和真实的退火类似:首先设定初温,然后慢慢降温,降温的同时让解对应的点移动。每次从当前点试图移动到另一个点时,若另一个点的函数值优于当前点的函数值,则接受这个移动,否则以一定的概率接受移动。这个概率随着温度的降低而减小。已有人证明,只要运行的时间足够长,模拟退火是一定可以获得最优解的。

1 设定初温T0 以及初始解S。
2 在S 的邻域内随机选取一个解S ′,若E(S ′) < E(S),则令
S=S ′,否则令Δt = E(S ′)-E(S),以T/T0的概率令S=S ′。
3 减小T 的值,若T< T1 则停止,否则转(2)。

我们可以在相同的温度下执行若干次步骤(2) 后再进行降温。模拟退火中有几个重要的参数:初始温度T0,相同温度下的迭代次数k,以及降温的方式(线性、指数)。如果参数设置得当,可能在很快就可以得到最优解或者非常优的次优解。反之可能运行几个小时都只能得到很差的解。考试时,我一般将T0 的值设在1000 ~10000 左右,降温的方式一般采取指数下降,即每次令T=a*T,a是一个小于1 的值,比如0.9、0.99999 等。而迭代次数根据考试时间(提交答案题)或者题目时限(传统题)而定。有时降温方式采取线性下降会取得更好的效果。
总而言之,考试时不要随意地选择参数,应该多试几组参数,通过向屏幕输出当前最优解来判断参数的好坏。

在此题中首先随机设定每个元素所在的组,然后求出初始的方差,然后开始退火,每次退火时首先随机选一个元素t,并得到它所在的组x,然后需要选一组y,把t放入组y中,若温度高的话,此时状态不稳定,很难使最终的搜索结果尽量更优,那么就需要贪心地选当前元素值之和最小的组。若温度低的话,此时状态比较稳定,而且各个组的元素和基本上差不多,此时就需要随机选个y,然后尝试将t放入组y中,若新解更优,则把旧解更新为新解,否则在[0,T0)随机一个数,若随机的数比T小,那么就把旧解更新为新解(T越小,在这种情况下更新解的概率也就越小,T的大小相当于是解的可靠性,T越小,说明此时解越可靠,在新解并不优的情况下改变原有解的概率也就越小),否则保持原有的解不变。

我们需要重复上述过程多次,就很可能得到最优解了。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

int n,m,pos[30],sum[30],a[30];
double ans,minans=1e9,T,avr;

void solve(){
    memset(sum,0,sizeof(sum));
    ans=0; T=10000;
    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]-avr)*(sum[i]-avr);
    while(T>0.1){
        T*=0.9;
        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;
        double tmp=ans;
        ans-=(sum[x]-avr)*(sum[x]-avr);
        ans-=(sum[y]-avr)*(sum[y]-avr);
        sum[x]-=a[t]; sum[y]+=a[t];
        ans+=(sum[x]-avr)*(sum[x]-avr);
        ans+=(sum[y]-avr)*(sum[y]-avr);
        if(ans<=tmp||rand()%10000<=T) pos[t]=y;
        else{
            sum[x]+=a[t]; sum[y]-=a[t]; ans=tmp;
        }
        minans=min(minans,ans);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),avr+=a[i];
    avr=avr/m;
    for(int i=1;i<=10000;i++) solve();
    printf("%.2f\n",sqrt(minans/m));
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章