bzoj2428[HAOI2006]均分数据

bzoj2428[HAOI2006]均分数据

题意:

已知N个正整数,将它们分成M组,使各组数据的数值和最平均,即各组的均方差最小,求最小均方差。

 

其中σ为均方差,-x-是各组数据和的平均值,xi为第i组数据的数值和。

题解:

神奇的模拟退火算法!它是爬山算法的加强版。爬山算法是一种贪心算法,对于每个状态,除非随机选出一个后继状态比它好,才会跳过去。但这样有可能“一叶障目不见泰山”,产生错误的答案。但模拟退火不一样,它如果找到一个后继状态比当前状态差,也有一定概率跳过去,但这概率和跳跃次数成反比。这样即使被“一叶障目”,也有机会绕过叶子,找到泰山。

关于爬山算法与模拟退火,有一个有趣的比喻:

  爬山算法:兔子朝着比现在高的地方跳去。它找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。

  模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。

这个算法写法实际上是模拟一个物理降温过程,然而对我这种物理渣~~只能背代码框架了QAQ

回到本题,首先给每个数据随机分到一个组,做5000次模拟退火。当温度比较高时跳跃不稳定,所以贪心一下,随机找一个数据,然后把它放进当前和最小的那个组;当温度渐低后,就随机找一个数据然后随机放组。关于退火的次数,网上题解里是10000,我试过1000会不能过,但是5000能过且比10000要快一倍,所以代码给出的是5000。

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <cstdlib>
 5 #include <cmath>
 6 #define inc(i,j,k) for(int i=j;i<=k;i++)
 7 #define INF 0x3fffffff
 8 using namespace std;
 9 
10 int n,m,pos[100]; double a[100],sum[100],ave,mn,T,ans,pre;
11 void solve(){
12      memset(sum,0,sizeof(sum)); inc(i,1,n)pos[i]=rand()%m+1,sum[pos[i]]+=a[i]; ans=0;
13      inc(i,1,m)ans+=(sum[i]-ave)*(sum[i]-ave); T=10000;
14      while(T>0.1){
15          pre=ans; int x=rand()%n+1,y;
16          if(T>500)y=min_element(sum+1,sum+1+m)-sum;else y=rand()%m+1; if(pos[x]==y)continue;
17          ans-=(sum[pos[x]]-ave)*(sum[pos[x]]-ave); ans+=(sum[pos[x]]-a[x]-ave)*(sum[pos[x]]-a[x]-ave); 
18         ans-=(sum[y]-ave)*(sum[y]-ave); ans+=(sum[y]+a[x]-ave)*(sum[y]+a[x]-ave);
19          if(rand()%10000+1>T&&ans>pre)ans=pre;else sum[pos[x]]-=a[x],sum[y]+=a[x],pos[x]=y;
20          T*=0.9;
21      }
22      if(ans<mn)mn=ans;
23 }
24 int main(){
25     scanf("%d%d",&n,&m); inc(i,1,n)scanf("%lf",&a[i]),ave+=a[i],swap(a[i],a[rand()%i+1]);
26     ave/=(double)m; mn=INF; inc(i,1,5000)solve();
27     printf("%.2lf",sqrt(mn/(double)m));
28 }

 

20160421

posted @ 2016-04-23 22:17  YuanZiming  阅读(398)  评论(0编辑  收藏  举报