浅谈模拟退火算法(SA)

最近被模拟退火整的焦头烂额,感觉有了一点小小的感悟,写一篇博客分享一下鄙人的一点拙见。

算法介绍

从退火谈起

退火是一种金属热处理工艺,指的是将金属缓慢加热到一定温度,保持足够时间,然后以适宜速度冷却。广义上说,退火是一种对材料的热处理工艺,包括金属材料、非金属材料。而且新材料的退火目的也与传统金属退火存在异同。

退火过程中,晶体分子的内能呈减小趋势,晶体分子的排布方式也由不断变化,如果这种状态比原状态内能小,啥也不用说,保留它,如果这种状态比原状态内能大,那么我们有一定的概率保留它,且概率随温度降低而降低(退火的精髓)

关于随机

c++<cstdlib>库中有一个非常可爱的函数rand,可以生成伪随机数,当然,随机数生成算法自己写也可以(个人不推荐),在计算机中,我们可以用rand来模拟概率问题。

模拟退火

应用范围

单调问题用不上他,二分就解决了。

单峰问题也用不到他,三分就解决了。

~但如果~解空间是这样的呢?

数据范围小的话,贪心也可以轻松解决。

但要是数据范围是阶乘级别的呢?

比如说构造类的问题,构造某一序列符合某个条件,解空间就是阶乘级别的,写深搜必然炸,写深搜剪枝,有点难度,写A*启发式搜索,反正我不会,这个时候,乱搞算法模拟退火就诞生了。

思路

模拟退火的本质就是用计算机模拟固体退火的过程,舍弃一定的正确率,换取还不错的时间效率。

我们可以以任意状态为初始状态,对于构造类对的题目,随机交换两个位置,看是否会更优,要是更优,我们就保存,否则,以exp(-ΔE/(kT))的概率接受,其中 E 为温度 T 时的内能,ΔE为其改变数, k 自己定义。

例题讲解

例题讲解采取从简单到困难的顺序

一平衡点 / 吊打XXX

题目描述

样例输入

3
0 0 1
0 2 1
1 1 1

样例输出

0.577 1.000

思路 

这是一道模拟退火的经典题,在平面上随机尝试几个点判断是否更优……

PS:这道题的参数很难调,加以改动可能会WA

AC代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;//快读
}
const double E = exp(1);
const double PI = acos(-1.0);
const int mod=1e9+7; 
const int maxn=2e3+10;
struct QwQ{
	int x,y,w;
}a[maxn];
int n,sx,sy;
double ansx,ansy,ans=1e18,t;
const double de=0.9965;
double cal(double x,double y){
	double res=0;
	for(int i=1;i<=n;i++){
		double tx=x-a[i].x,ty=y-a[i].y;
        res+=sqrt(tx*tx+ty*ty)*a[i].w;
	}
	return res;
}
void sa(){
	double x=ansx,y=ansy;t=3000;
	while(t>1e-15){
		double xx=x+((rand()<<1)-RAND_MAX)*t;
		double yy=y+((rand()<<1)-RAND_MAX)*t;
		double now=cal(xx,yy);double d=now-ans;
		if(d<0){
			x=xx,y=yy,ansx=x,ansy=y,ans=now;
		}
		else if(exp(-d/t)*RAND_MAX>rand()) x=xx,y=yy;
		t*=de;
	}
}
void solve(){
	ansx=(double)sx/n,ansy=(double)sy/n;ans=cal(ansx,ansy);
	sa();sa();sa();sa();sa();
}
signed main() {
    n=read();
    for (int i=1;i<=n;i++) {
        a[i].x=read(),a[i].y=read(),a[i].w=read();
        sx+=a[i].x,sy+=a[i].y;
    }
    solve();
    printf("%.3f %.3f\n",ansx,ansy);
    return 0;
}

 二 Haywire

题目描述

样例输入

6
6 2 5
1 3 4
4 2 6
5 3 2
4 6 1
1 5 3

样例输出

17

思路 

此处涉及两个模拟退火小技巧

一.为了获得更高的正确率,尽可能多的跑模拟退火,卡时间跑模拟退火

二.除随机交换外random_shuffle();函数可以随机打乱数组,节约了码量

AC代码

#include <bits/stdc++.h>
using namespace std;
#define gc getchar()
double st=clock();
int n,ans=0x3f3f3f3f; 
int a[100];
int mp[100][100];
int m[100][100];

inline int read(){
	int r=0,l=1;char ch=gc;
	while(!isdigit(ch)){if(ch=='-')l=-1;ch=gc;}
	while(isdigit(ch)){r=(r<<3)+(r<<1)+ch-'0';ch=gc;}
      int ou=r*l;
	return ou;
}

inline double time(){
	return (clock()-st)/1e6;//返回系统运行时间
}

inline void solve(){
	random_shuffle(a+1,a+n+1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(m[a[i]][a[j]])
           		   mp[a[i]][a[j]]=abs(j-i);
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cnt+=mp[i][j];
	ans=min(ans,cnt/2);
}

int main(){
	srand(time(NULL));
	n=read();
	for(int i=1;i<=n;i++)a[i]=i;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=3;j++)
			m[i][read()]=1;
	while(time()<0.985)solve();//卡时
	printf("%d\n",ans);
	return 0;
}

 三 均分数据

题目描述

样例输入

6 3
1 2 3 4 5 6

样例输出

0.00

思路 

随机生成数组,然后贪心,看能不能更优

AC代码

#include<bits/stdc++.h>
using namespace std;
double anss=1000000000,pin,sum,a[21],b[21];
int n,m,minn,wei;
void print()
{
	double lans=0;
	sum=0;
	for(int i=1;i<=m;i++)
	sum+=b[i];
	pin=(double)sum/(double)m;
	for(int i=1;i<=m;i++)
	{
		lans+=(pin-b[i])*(pin-b[i]);
	}
	lans/=m;
	lans=sqrt(lans);
	if(lans<anss)
	anss=lans;
}
int main()
{
	srand(time(0));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=1000000;i++)
	{
		memset(b,0,sizeof(b));
		random_shuffle(a+1,a+n+1);
		for(int j=1;j<=n;j++)
	    {
	    	minn=b[1];
	    	wei=1;
	    	for(int k=2;k<=m;k++)
	    	{
	    	if(minn>b[k])
	    	minn=b[k],wei=k;
			}
			b[wei]+=a[j];
		}
		print();
	}
	printf("%.2lf",anss);
    return 0;
}

四 锯木厂选址

题目描述

样例输入

9 
1 2 
2 1 
3 3 
1 1 
3 2 
1 6 
2 1 
1 2 
1 1

样例输出

26

思路 

每一次位移就是将a和b向左或右移动一定距离,如果小于0就加上n,大于n就减去n。

记得开long long。

据说这道题能DP过,谁知道呢

AC代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=210000;
long long w[N],d[N],s,n;
template<typename T>
inline void read(T &x)
{
    x=0;char c = getchar();int s = 1;
    while(c < '0' || c > '9') {if(c == '-') s = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c -'0';c = getchar();}
    x*=s;
}
template<typename T>
inline void write(T x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
    return;
}
struct node
{
	int a,b;
	inline double f()
	{
		if(a>b)swap(a,b);
		return w[a]*d[a]+(w[b]-w[a])*d[b]+(w[n]-w[b])*d[n+1]-s;
	}
}x,best;
const double delta=0.99,eps=5e-1;
double T;
int main()
{
	srand(99);
	read(n);
	w[0]=0;d[0]=0;s=0;
	for(int i=1;i<=n;i++)
	{
		int a,b;
		read(a),read(b);
		w[i]=w[i-1]+a;d[i+1]=d[i]+b;
		s=s+a*d[i];
	}
	best.a=1;best.b=2;
	int times=150;
	while(times--)
	{
		T=1.0*n;
		x.a=rand()%n+1;
		x.b=rand()%n+1;
		while(T>=eps)
		{
			int aa=round((2.0*rand()/RAND_MAX-1)*T);
			int bb=round((2.0*rand()/RAND_MAX-1)*T);
			node x1;
			x1.a=((x.a+aa)%n+n)%n;
			x1.b=((x.b+bb)%n+n)%n;
			if(x1.f()<x.f())x=x1;
			else
			{
				double gl=1.0*rand()/RAND_MAX;
				if(gl<=exp((x.f()-x1.f())/T))x=x1;
			}
			if(x.f()<best.f())best=x;
			T=T*delta;
		}
	}
	write((long long)(best.f()));
	return 0;
}

这是我的第五篇文章,如有纰漏也请各位大佬指正

辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值