【Luogu P2503】均分数据

题目描述

已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:

σ=mi=1(Six)2m,x=mi=1xim σ = ∑ i = 1 m ( S i − x ) 2 m , x = ∑ i = 1 m x i m

题解

胡乱代一遍式子发现答案只与 S2i ∑ S i 2 有关。
于是要最小化它。又均值定理可知应使每个Si尽量相等。
于是构造新解的方法就出来了,每次从其他地方取一个加到当前最小组里。
我这里为了方便先分配了一个比较优的组。

但是单这样会全WA,你会发现你找出来的新解总是不比你当前这个解优,并且试一下发现自己总是接受不了比自己差的解,于是就找不到最优解了。

于是我把接受差解的不等式右边除以1000就AC了? (迷)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<ctime>
#define POW(a) ((a)*(a))
using namespace std;
int n,m;
typedef double db;
const int N=40;
int a[N];
int K[N];
int Sum[10];
const db INF=1e99;
const db eps=1e-10;
const db delta=0.99;
const db T=1000000;
db ax;
db Ans=0;
int ans;
//使sigma xi^2 最小
int S=0;
inline int Rand(int mod){return rand()%mod+1;}
int top=0;
int st[N];
inline void Get(int k){top=0;for(register int i=1;i<=n;++i) if(K[i]==k) st[++top]=i;}
inline db calc(){
    register db S=0;
    for(register int i=1;i<=m;++i) S+=POW(Sum[i]);
    return S;
}
inline void SA()
{
    db t=T;
    int nans=ans;
    while(t>eps){
        bool flag=1;
        while(flag){
            flag=0;register int min_pos=-1;register int max_pos=-1;
            for(register int i=1;i<=m;++i) if(min_pos==-1||(Sum[min_pos]>Sum[i])) min_pos=i;
            register int p=Rand(n);
            while(K[p]==min_pos) p=Rand(n);
            max_pos=K[p];
            K[p]=min_pos;Sum[min_pos]+=a[p];Sum[max_pos]-=a[p];
            register int new_ans=calc();
            if(new_ans<nans) {nans=new_ans;if(nans<ans) ans=nans;flag=1;}
            else if(rand()>exp((new_ans-nans)/t)*RAND_MAX/1000.0000) nans=new_ans,flag=1;//....
            else{
                K[p]=max_pos;Sum[max_pos]+=a[p];Sum[min_pos]-=a[p];
            }
        }
        t*=delta;
    }

}
int main()
{
    srand(19260817);
    scanf("%d %d",&n,&m);register int SS=0;
    for(register int i=1;i<=n;++i) scanf("%d",&a[i]),SS+=a[i];
    ax=(db)(SS)/(m*1.0000);
    sort(a+1,a+1+n);
    Ans=1.000*m*POW(ax)-(2*SS*1.0000)*ax;
    register int num=(n-1)/m+1;
    register int k=0;
    bool g=0;int l=1;int r=n;
    register int tot=0;
    for(register int i=1;i<=m;++i)
    {
        while(k<num&&r>=l){
            if(!g) g=1,K[l]=i,Sum[i]+=a[l++];
            else g=0,K[r]=i,Sum[i]+=a[r--];
            ++k;++tot;
        }
        k=0;
    }
    for(register int i=1;i<=m;++i) S+=POW(Sum[i]);
    ans=S;SA();
    printf("%.2lf\n",sqrt((1.000*ans+Ans)/(1.000*m)));
}
/*
6 2
1 2 3 4 5 6
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值