zoukankan      html  css  js  c++  java
  • BZOJ2428:[HAOI2006]均分数据

    我对模拟退火的理解:https://www.cnblogs.com/AKMer/p/9580982.html

    题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=2428

    遗传算法和爬山算法在(OJ)(A)题的可能性太小了,还是退火好,可以对我在(BZOJ)的账号做一点贡献。

    对于这道题,我们首先得明白状态空间是啥。

    这道题要求一个最优的分组方法,使得每一组的权值和的方差最小,那么状态就是(n)个数分别放在哪一个组里。

    然后我们就可以用一个长度为(n)的数组来记录状态,寻找邻居节点的时候随机一个数字,将它放到另一组去就是一个新状态了。然后对于方差最小这个要求,我们可以灵性的贪心一波,比如每次都把这个数字移到权值和最小的那一组去。这样在很大几率上会直接减少方差。

    每个状态对应的权值就是该状态分配下的方差。

    然后退个六七十遍火就是了。

    时间复杂度:(O(能A))

    空间复杂度:(O(能A))

    代码如下:

    #include <cmath>
    #include <ctime>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define sqr(a) ((a)*(a))
    
    const int maxn=30;
    
    const double T_0=1e-7,del_T=0.998;
    
    int n,m;//n个数字,m组
    double ave,ans;//ave存平均值,ans存历史最小方差
    int a[maxn],d[maxn],b[maxn],c[maxn],e[maxn];//a存数字,b[i]存第i个数字当前在第几组,d[i]存第i个数字在历史最优状态下在第几组,c[i]存第i组当前权值,e[i]存第i组在历史最优状态下的权值
    
    int read() {
    	int x=0,f=1;char ch=getchar();
    	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    	for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    	return x*f;
    }//快读
    
    double calc(int pos,int group) {//计算把pos号数字放到第group组后的方差
    	double sum=0;
    	for(int i=1;i<=m;i++) {
    		double tmp=c[i]-ave;
    		if(tmp==b[pos])tmp-=a[pos];
    		if(tmp==group)tmp+=a[pos];
    		sum+=sqr(tmp);//按照数学公式计算
    	}
    	return sqrt(sum/m);//记得返回double,不然会被卡精度
    }
    
    void Anneal() {
    	memcpy(b,d,sizeof(d));
    	memcpy(c,e,sizeof(e));//每次都从最优状态开始
    	double T=1e7;//初始温度1e7,结束温度1e-7,del_T为0.998;
    	while(T>=T_0) {
    		int group=0,mn=1e9;
    		for(int i=1;i<=m;i++)
    			if(c[i]<mn)mn=c[i],group=i;//找最小的那一组编号
    		int pos=rand()%n+1;
    		double tmp=calc(pos,group);//计算新状态的答案
    		if(tmp<ans) {//如果新状态更优
    			c[b[pos]]-=a[pos];c[group]+=a[pos];
    			b[pos]=group;ans=tmp;
    			memcpy(d,b,sizeof(b));
    			memcpy(e,c,sizeof(c));//直接更新当前状态以及历史最优状态
    		}
    		else if(exp((ans-tmp)/T)*RAND_MAX>rand()) {//如果在一定的概率内
    			c[b[pos]]-=a[pos];c[group]+=a[pos];
    			b[pos]=group;//直接更新当前状态
    		}
    		T*=del_T;//降温
    	}
    }
    
    int main() {
    	srand(time(0));
    	n=read();m=read();
    	for(int i=1;i<=n;i++) {
    		a[i]=read();
    		b[i]=d[i]=rand()%m+1;
    		e[d[i]]+=a[i];
    		c[d[i]]+=a[i];ave+=a[i];
    	}ave/=m;ans=calc(0,0);//初始化一个状态
    	for(int i=1;i<=60;i++)
    		Anneal();//做60遍退火(好像这个代码稍微少一点点就A不了了)
    	printf("%.2lf
    ",ans);//输出答案
    	return 0;
    }
    

    这题也可以用爬山写,和这个代码差不多,大家灵性脑补一波就好。

  • 相关阅读:
    在Mac系统下使用自己安装的PHP
    在一个文件里追加内容和换行
    Linux系统下如何去掉文件的@属性
    composer的安装和使用
    Git SSH Key 生成步骤
    自定义mysql函数时报错,[Err] 1418
    百度echarts
    linux 内存释放命令
    第二届PHP全球开发者大会(含大会的PPT)
    在CentOS上安装Java开发环境:使用yum安装jdk
  • 原文地址:https://www.cnblogs.com/AKMer/p/9588081.html
Copyright © 2011-2022 走看看