简介
\(\quad\)模拟退火是一种随机化算法。当一个问题的方案数量极大(甚至是无穷的)而且不是一个单峰函数时,我们常使用模拟退火求解。
实现
\(\quad\)很多时候在一个区域内我们需要跳出局部最优解,去接受某些非最优解,才有可能取到全局最优解。
\(\quad\)模拟算法概括:如果新状态的解更优则修改答案,否则以一定概率接受新的状态。
\(\quad\)我们定义当前的温度为 \(T\) ,新状态与已知状态(由已知状态通过随机方式得到)之间的能量差值为 \(\Delta E(\Delta E\ge 0)\),则发生状态转移(修改最优解)的概率为
(exp(-delta/t)*RAND_MAX>rand())
\(\quad\)一定要这么写
注意:
\(\quad\)有时我们为了使得到的解更有质量,会在模拟退火结束后,以当前温度在得到的解附近多次随机状态,尝试得到更有的解。
具体实现
\(\quad\)模拟退火一般有三个参数:
- 初始温度 \(T_0\)
- 降温系数 \(d\)
- 终止温度 \(T_k\)
\(\quad\)其中 \(T_0\) 是一个比较大的数, \(d\) 是一个非常接近 1 但是小于 1 的数,\(T_k\) 是一个接近 0 的正数
\(\quad\)首先让温度 \(T=T_0\),然后按照上述步骤尝试转移,再让 \(T=d\times T\),当 \(T<T_k\) 时,模拟退火过程结束,当前最优解即为最终最优解。
注意:
\(\quad\)模拟退火本身就是一个玄学的做法,所以你很有可能几次评测都是不同的分值(看你的 rp 了)
\(\quad\)注意为了使得解更为精确,我们通常不直接取当前解作为答案,而是在退火过程中维护遇到的所有解的最优值。
(引用一张 Wiki - Simulated annealing 的图)
一些技巧
分块模拟退火
\(\quad\)有时函数的峰很多,模拟退火难以跑出最优解。
\(\quad\)此时可以把整个值域分成几段,每段跑一遍模拟退火,然后再取最优解。
卡时
用\(\quad\) clock 函数,一直跑模拟退火,直到快要超时,就直接退出去。
例题
Luogu 1337 [JSOI2004]平衡点 / 吊打XXX
\(\quad\)很明显当
\[\sum dis\times w \]\(\quad\)最小时,肯定更平衡。
\(\quad\)我们随机化坐标。
\(\quad\)一般下一次的选择都可以这么写
double nxtx=nowx+t*(rand()*2-RAND_MAX);
double nxty=nowy+t*(rand()*2-RAND_MAX);
code:
#include<bits/stdc++.h>
#define down 0.996
using namespace std;
const int maxn=10005;
int n,x[maxn],y[maxn],w[maxn];
double ansx,ansy,dis;
double calc(double xx,double yy)
{
double res=0,dx,dy;
for(int i=1;i<=n;i++)
{
dx=x[i]-xx,dy=y[i]-yy;
res+=sqrt(dx*dx+dy*dy)*w[i];
}
return res;
}
void SA()
{
double t=3000;
double nowx=ansx,nowy=ansy;
while(t>1e-15)
{
double nxtx=nowx+t*(rand()*2-RAND_MAX);
double nxty=nowy+t*(rand()*2-RAND_MAX);
double ew=calc(nxtx,nxty);
double delta=ew-dis;
if(delta<0) nowx=nxtx,nowy=nxty,dis=ew,ansx=nxtx,ansy=nxty;
else if(exp(-delta/t)*RAND_MAX>rand()) nowx=nxtx,nowy=nxty;
t*=down;
}
}
int main()
{
srand(time(0));
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i]>>w[i];
ansx+=x[i];
ansy+=y[i];
}
ansx/=n;
ansy/=n;
dis=calc(ansx,ansy);
SA();SA();SA();SA();
printf("%.3lf %.3lf\n",ansx,ansy);
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int T,a[35],best,n;
int calc()
{
int s1=0,s2=0;
for(int i=1;i<=(n+1)/2;i++) s1+=a[i];
for(int i=(n+1)/2+1;i<=n;i++) s2+=a[i];
return abs(s1-s2);
}
int main()
{
srand(rand());
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
best=0x3f3f3f3f;
for(int k=1;k<=1000;k++)
{
for(register double t=5000;t>1e-10;t*=0.911)
{
int x=rand()%n+1;
int y=rand()%n+1;
swap(a[x],a[y]);
int tmp=calc();
if(tmp<best) best=tmp;
else if(exp((best-tmp)/t)*RAND_MAX<(double)rand()) swap(a[x],a[y]);
}
}
cout<<best<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int n,m,a[25],s;
double no[25],be[25];
double ave,best,now;
double calc()
{
double num[10];
memset(num,0,sizeof(num));
for(int i=1;i<=n;i++)
{
int p=1;
for(int j=1;j<=m;j++) if(num[j]<num[p]) p=j;
num[p]+=no[i];
}
double tmp=0;
for(int i=1;i<=m;i++) tmp+=(num[i]-ave)*(num[i]-ave);
return sqrt((double)tmp/m);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],no[i]=be[i]=a[i],s+=a[i];
ave=(double)s/m;
best=calc();
for(int k=1;k<=30;k++)
{
now=best;
for(int i=1;i<=n;i++) no[i]=be[i];
for(double t=100000;t>=1e-14;t*=0.996)
{
int x=rand()%n+1;
int y=rand()%n+1;
while(x==y) y=rand()%n+1;
swap(no[x],no[y]);
double tmp=calc();
if(tmp<best)
{
best=tmp;
for(int i=1;i<=n;i++) be[i]=no[i];
}
if(tmp<now || exp((tmp-now)/t)*RAND_MAX>rand()) now=tmp;
else swap(no[x],no[y]);
}
}
printf("%.2f",best);
return 0;
}
参考文献(致谢):
OI WIKI