[HAOI2006]均分数据

六眼飞鱼酱① 提交于 2019-12-01 08:00:50

洛咕

题意:已知\(n(n<=20)\)个正整数:\(a_1,a_2,……,a_n\) .今要将它们分成\(m(2<=m<=6)\)组,使得各组数据的数值和最平均,即各组的均方差最小.均方差公式如下:

分析:首先根据公式各组数据和的平均值是\(ave\)已知的,即\((\sum_{i=1}^na[i])/m\),所以要最小化均方差,我们只需要最小化\(\sum_{i=1}^m(sum_i-ave)^2\)就好了,其中\(sum_i\)表示分成的第i段的和,这个显然是可以\(dp\)求的.设\(f[i][j]\)表示将序列前i个数分成j段,\(\sum_{i=1}^j(sum_i-ave)^2\)的最小值.那么\(f[i][j]=min(f[i][j],f[k][j-1]+(s[i]-s[k]-avs)^2)\),其中\(s[]\)表示序列的前缀和.

那么现在的问题就是如何找到"序列"呢?因为根据题意这n个正整数是可以任意排列的.所以就要用到模拟退火来随机出排列.

这道题的各种参数还比较好调,一下就过去了.

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<map> #include<set> #define ll long long using namespace std; inline int read(){     int x=0,o=1;char ch=getchar();     while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();     if(ch=='-')o=-1,ch=getchar();     while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();     return x*o; } const int N=21; int n,m,tot,a[N],sum[N]; double ave,ans,f[N][8]; inline double dp(){     for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];//预处理前缀和     for(int i=0;i<=n;++i)for(int j=0;j<=n;++j)f[i][j]=0x3f;     f[0][0]=0;//初始化,其余为无穷大值     for(int i=1;i<=n;++i){         for(int j=1;j<=m;++j){             for(int k=0;k<i;++k)                 f[i][j]=min(f[i][j],f[k][j-1]+(sum[i]-sum[k]-ave)*(sum[i]-sum[k]-ave));         }     }     return (double)sqrt(f[n][m]*1.0/m);//根据公式来求均方差 } inline void mnth(){     double T=2333,eps=1e-6;     while(T>eps){         int x=rand()%n+1,y=rand()%n+1;//随机出两个数组下标         if(x==y){T*=0.998;continue;}         swap(a[x],a[y]);         double now=dp(),delta=now-ans;         if(delta<0)ans=now;         else if(exp(-delta/T)*RAND_MAX>rand())ans=now;         else swap(a[x],a[y]);//不能更新答案的话记得换回来         T*=0.998;     } } int main(){     srand((int)time(NULL));n=read();m=read();     for(int i=1;i<=n;++i)a[i]=read(),tot+=a[i];     ave=(double)tot*1.0/m;ans=dp();mnth();printf("%.2lf\n",ans);     return 0; } 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!