最近被模拟退火整的焦头烂额,感觉有了一点小小的感悟,写一篇博客分享一下鄙人的一点拙见。
算法介绍
从退火谈起
退火是一种金属热处理工艺,指的是将金属缓慢加热到一定温度,保持足够时间,然后以适宜速度冷却。广义上说,退火是一种对材料的热处理工艺,包括金属材料、非金属材料。而且新材料的退火目的也与传统金属退火存在异同。
退火过程中,晶体分子的内能呈减小趋势,晶体分子的排布方式也由不断变化,如果这种状态比原状态内能小,啥也不用说,保留它,如果这种状态比原状态内能大,那么我们有一定的概率保留它,且概率随温度降低而降低(退火的精髓)。
关于随机
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;
}
这是我的第五篇文章,如有纰漏也请各位大佬指正
辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。