原题链接: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}}
σ=M∑i=1M(xi−x)2
x
‾
=
∑
i
=
1
M
x
i
M
\overline{x}=\frac{\sum_{i=1}^M x_{i}}{M}
x=M∑i=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<bits/stdc++.h>
#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;
}