[HAOI2006]均分数据

洛咕

题意:已知\(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
#include
#include
#include
#include
#include
#include
#include
#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;keps){
        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;
}

你可能感兴趣的:([HAOI2006]均分数据)