Description
已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:
,,其中σ为均方差,是各组数据和的平均值,xi为第i组数据的数值和。
Input
Output
这一行只包含一个数,表示最小均方差的值(保留小数点后两位数字)。
Sample Input
1 2 3 4 5 6
Sample Output
HINT
对于全部的数据,保证有K<=N <= 20,2<=K<=6
我真的是闲的蛋疼才会去搞模拟退火这个东西
这道题算是模拟退火入门题吧。。。
先说说做这道题之前,我想了一个很垃圾的DP,不知道能不能过
然后就没想法了,于是去%题解:模拟退火
关于这个早有耳闻,据说很考RP
那么他其实还是一个贪心,就是加了随机性:在前期,变量很大,我们取得了一个较优解,但是还是有很大几率接受一个较差解
然后随着时间推进,我们没有时间限制可以败家了,然后把变量改小,取得较差解的几率越来越低,然后的出正确答案
这就是模拟退火
再看这道题,他的n才20,k也才。。卧槽哪来的k?,也不过6
那我们就可以败家一点,给他4000次机会(当然这个数字不确定),每次随机组合,然后随机选数,进行计算,然后随机接受解
在这道题里也有个细节,我们取得了一个较优解,就得保存一下当前这个随机选出来的数,交换位置
然后继续随机进行解。。
神tm这AC也很随机吧。。。
代码如下:
#include<ctime> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int n,m; int a[30],unit[30]; double sum[30],tmp,ans=1e30,ave; double sqr(double x) { return x*x; } void solve() { memset(sum,0,sizeof(sum));tmp=0; for(int i=1;i<=n;i++)unit[i]=rand()%m+1; for(int i=1;i<=n;i++)sum[unit[i]]+=a[i]; for(int i=1;i<=m;i++)tmp+=sqr(sum[i]-ave); double T=17500; while(T>0.1) { T*=0.9; int t=rand()%n+1,x=unit[t],y; if(T>500)y=min_element(sum+1,sum+m+1)-sum; else y=rand()%m+1; if (x==y)continue; double last=tmp; tmp-=sqr(sum[x]-ave);tmp-=sqr(sum[y]-ave); sum[x]-=a[t];sum[y]+=a[t]; tmp+=sqr(sum[x]-ave);tmp+=sqr(sum[y]-ave); if(rand()%10000>T&&tmp>last)sum[x]+=a[t],sum[y]-=a[t],tmp=last; else unit[t]=y; } ans=min(ans,tmp); } int main() { srand(0); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]),ave+=a[i],swap(a[i],a[rand()%i+1]); ave/=m; for (int i=1;i<=4000;i++) solve(); printf("%.2lf\n",sqrt(ans/m)); return 0; }
by_lmy