BZOJ2428[HAOI2006] 均分数据

原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2428

均分数据

Description

已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:
σ = ∑ i = 1 M ( x i − x ‾ ) 2 M \sigma= \sqrt{\frac{\sum_{i=1}^M(x_{i}-\overline{x})^2}{M}} σ=Mi=1M(xix)2
x ‾ = ∑ i = 1 M x i M \overline{x}=\frac{\sum_{i=1}^M x_{i}}{M} x=Mi=1Mxi
,其中σ为均方差, x ‾ \overline{x} x是各组数据和的平均值, x i x_{i} xi为第i组数据的数值和。

Input

第一行是两个整数,表示N,M的值(N是整数个数,M是要分成的组数)
第二行有N个整数,表示A1、A2、……、An。整数的范围是1–50。
(同一行的整数间用空格分开)

Output

这一行只包含一个数,表示最小均方差的值(保留小数点后两位数字)。

Sample Input

6 3

1 2 3 4 5 6

Sample Output

0.00

HINT

对于全部的数据,保证有K<=N <= 20,2<=K<=6

题解

模拟退火。。。
因为爬山算法有许多毛病,很容易陷入局部最优解无法自拔,所以模拟退火在启发式搜索时,对于没有当前解优的解仍有概率接受,所以不会像爬山算法一样被困在小山顶或者在山脊两边来回震荡,而是有几率脱离局部最优解。
在这个问题中,我们先随机分配数据,每次随机将一个元素移动到另一组去,如果移动后方差更小,那么接受此次移动;如果方差更大,我们便随机生成一个数,如果该数低于当前温度,那么接受该不优解。这样保证在退火初期震荡较大时能够脱离局部最优解,随着时间流逝,温度降低,最优解也逐渐趋近正确答案,这时我们接受不优解的概率也变小。
另外,在退火初期,温度高,答案的震荡较大,为了避免乱跳,我们可以采用贪心策略,将元素移动到当前最小的一组中去;当温度较低时,最优解趋于正确,我们再随机调整。
这种乱搞启发式的算法当然不能保证一次正确,比较保险的是进行10000+次退火,然而RP不好的博主交洛谷的时候WA了一个点,改了个随机种子就A了。。。事实证明RP才是王道呀orz。

代码
#include
#define db double
using namespace std;
const int M=25;
int x[M],sum[M],team[M],n,m;
db ave,minn=1e30;
db sqr(db x){return x*x;}
void in()
{
    srand(20021016);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    scanf("%d",&x[i]),ave+=x[i];
    ave/=double(m);
}
void sa()
{
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;++i)
    {
        team[i]=rand()%m+1;
        sum[team[i]]+=x[i];
    }
    db ans=0,T=10000,tmp;
    for(int i=1;i<=m;++i)
    ans+=sqr(sum[i]-ave);
    int j,f,t;
    while(T>0.1)
    {
        T*=0.9;
        j=rand()%n+1;
        f=team[j];
        if(T>500)t=min_element(sum+1,sum+1+m)-sum;
        else t=rand()%m+1;
        if(f==t) continue;
        tmp=ans;
        tmp-=sqr(sum[f]-ave);
        tmp-=sqr(sum[t]-ave);
        sum[f]-=x[j];sum[t]+=x[j];
        tmp+=sqr(sum[f]-ave);
        tmp+=sqr(sum[t]-ave);
        if(tmp<=ans) ans=tmp,team[j]=t;
        else if(rand()%10000>T) sum[f]+=x[j],sum[t]-=x[j];
        else team[j]=t,ans=tmp;
    }
    minn=min(ans,minn);
}
void ac()
{
    for(int i=1;i<=10000;++i)sa();
    printf("%.2lf",sqrt(minn/m));
}
int main()
{
    in();
    ac();
    return 0;
}

你可能感兴趣的:(模拟退火,杂============,数论&数学========,随机化)