bzoj2428 [HAOI2006]均分数据 模拟退火
[HAOI2006]均分数据
Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 3434 Solved: 1091
[Submit][Status][Discuss]
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
1 2 3 4 5 6
Sample Output
0.00
HINT
对于全部的数据,保证有K<=N <= 20,2<=K<=6
Source
HOME Back
以前也没怎么写过模拟退火,这道题让我知道了一些怎么写
退了10000次火,模拟退火主要靠感觉的吧,
写法也是相当奇怪的
当温度很高的时候,将随机出来的一个数,放入当前组里最小的一组,
当温度比较低的时候,就随机放在一组里,然后判断交换后答案是否更优,是的话就交换,不然随机交换。
1 #include<cstring> 2 #include<cstdio> 3 #include<algorithm> 4 #include<iostream> 5 #include<cmath> 6 7 #define N 10007 8 #define ll long long 9 using namespace std; 10 inline int read() 11 { 12 int x=0,f=1;char ch=getchar(); 13 while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} 14 while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} 15 return x*f; 16 } 17 18 int n,m; 19 int sum[N],a[N],belong[N]; 20 double minans=1e30,ave; 21 22 void SA() 23 { 24 memset(sum,0,sizeof(sum)); 25 for (int i=1;i<=n;i++) 26 { 27 belong[i]=rand()%m+1; 28 sum[belong[i]]+=a[i]; 29 } 30 double ans=0; 31 for (int i=1;i<=m;i++) 32 ans+=(sum[i]-ave)*(sum[i]-ave); 33 double T=10000; 34 while(T>0.1) 35 { 36 T*=0.9; 37 int t=rand()%n+1,x=belong[t],y; 38 if (T>500) y=min_element(sum+1,sum+m+1)-sum; 39 else y=rand()%m+1; 40 if (x==y) continue; 41 double tmp=ans; 42 ans-=(sum[x]-ave)*(sum[x]-ave); 43 ans-=(sum[y]-ave)*(sum[y]-ave); 44 sum[x]-=a[t],sum[y]+=a[t]; 45 ans+=(sum[x]-ave)*(sum[x]-ave); 46 ans+=(sum[y]-ave)*(sum[y]-ave); 47 if (ans<=tmp) belong[t]=y; 48 else if (rand()%10000>T) sum[x]+=a[t],sum[y]-=a[t],ans=tmp; 49 else belong[t]=y; 50 } 51 if (ans<minans) minans=ans; 52 } 53 int main() 54 { 55 srand(19260817); 56 n=read(),m=read(); 57 for (int i=1;i<=n;i++) 58 a[i]=read(),ave+=a[i]; 59 ave/=(double)m; 60 for (int i=1;i<=10000;i++) SA(); 61 printf("%.2lf\n",sqrt(minans/m)); 62 }