bzoj2428 均分数据 爬山算法or模拟退火

       爬山法和模拟退火的本质是差不多的,在本题中就是得到一个初解后,随机一个数改变到哪一组(组别在步长较大的时候选择和最小的,步长较小的时候随机选择)中,然后比较是否更优,更优就跳过去;如果不更优,则模拟退火算法有一定概率接受。

       注意由于模拟退火有一定概率接受,因此(很)可能得不到最好的结果,可以把步长定的小一点然后多跑几遍;爬山算法则不同,一定每次都是局部最优解,可以把步长定的大点,这样搜到最优解的可能性就大了,算法可以少跑几遍。

       注意在x改变组别的时候不需要重新计算,只需要计算它带来的影响,然后这个影响可以用平方差公式后提取公因式化简。

AC代码如下:

(爬山算法)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;

int n,m,blg[105]; double ave,a[105],sum[105];
double solve(){
	sum[0]=1e100; int i;
	for (i=1; i<=m; i++) sum[i]=0;
	for (i=1; i<=n; i++){ blg[i]=rand()%m+1; sum[blg[i]]+=a[i]; }
	double t=1e5,last,ans=0; 
	for (i=1; i<=m; i++) ans+=(sum[i]-ave)*(sum[i]-ave);
	for (; t>1e-3; t*=0.98){
		last=ans;
		int x=rand()%n+1,y=0;
		if (t<500) y=rand()%m+1; else
			for (i=1; i<=m; i++) if (sum[i]<sum[y]) y=i;
		ans+=a[x]*(a[x]+sum[y]-sum[blg[x]])*2;
		if (ans>last) ans=last;
		else{ sum[blg[x]]-=a[x]; blg[x]=y; sum[y]+=a[x]; }
	}
	return ans;
}
int main(){
	srand(20160310);
	scanf("%d%d",&n,&m); int i; double ans=1e100;
	for (i=1; i<=n; i++){
		scanf("%lf",&a[i]); ave+=a[i];
	}
	ave/=m;
	for (i=1; i<=1000; i++) ans=min(ans,solve());
	printf("%.2f\n",sqrt(ans/m));
	return 0;
}



(模拟退火)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;

int n,m,blg[105]; double ave,a[105],sum[105];
double solve(){
	sum[0]=1e100; int i;
	for (i=1; i<=m; i++) sum[i]=0;
	for (i=1; i<=n; i++){ blg[i]=rand()%m+1; sum[blg[i]]+=a[i]; }
	double t=1e4,last,ans=0; 
	for (i=1; i<=m; i++) ans+=(sum[i]-ave)*(sum[i]-ave);
	for (; t>1e-1; t*=0.75){
		last=ans;
		int x=rand()%n+1,y=0;
		if (t<500) y=rand()%m+1; else
			for (i=1; i<=m; i++) if (sum[i]<sum[y]) y=i;
		//ans+=(sum[blg[x]]-a[x]-ave)*(sum[blg[x]]-a[x]-ave)-(sum[blg[x]]-ave)*(sum[blg[x]]-ave);
		//ans+=(sum[y]+a[x]-ave)*(sum[y]+a[x]-ave)-(sum[y]-ave)*(sum[y]-ave);
		ans+=a[x]*(a[x]+sum[y]-sum[blg[x]])*2;
		if (ans>last && t<rand()%10000) ans=last;
			else{ sum[blg[x]]-=a[x]; blg[x]=y; sum[y]+=a[x]; }
	}
	return ans;
}
int main(){
	srand(20160310);
	scanf("%d%d",&n,&m); int i; double ans=1e100;
	for (i=1; i<=n; i++){
		scanf("%lf",&a[i]); ave+=a[i];
	}
	ave/=m;
	for (i=1; i<=50000; i++) ans=min(ans,solve());
	printf("%.2f\n",sqrt(ans/m));
	return 0;
}


by lych

2016.3.10

你可能感兴趣的:(模拟退火,爬山算法)