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