浅谈模拟退火

模拟退火,很有力的一个偏分武器。

对于一类搜索,或者是有明确答案空间的题目,且不会写正解,爆搜过不去,剪枝减不了的时候,我们可以考虑用模拟退火偏分。

模拟退火模拟的是一个物理过程,即一个物体由高温逐步降温的过程。也可以理解为,在我们寻找答案的过程中,答案所存在的范围越来越小,以此类比降温。

也就是说,我们先开辟一个温度,或者叫搜索空间,在这个空间中,我们随机一个答案,判断这个答案和当前已知答案的优劣。

若优于当前解,显然我们必然要更新的。如果比当前解劣呢?

注意到,我们随机的答案是和上一个答案有关联的,有时候我们随机的将会是一个与原答案的变化幅度。也就是说,一个答案,虽然它劣,但是它的周围是可能有更优解的。

有一个选择的准则:如果当前解优则更新;否则:

定义(delta) ( ext{t'})为当前答案与新答案的变化幅度。则,我们以(exp(frac{-delta t'}{T}))的概率来接受这个答案。(T)表示当前的“温度”。

我不会证这东西qwq

然而有很多题没有这个准则,只更新更优解也可以过

于是,我们得到一个基本算法模型:

首先,确定温度(T).

然后,确定降温系数。

使得(T)在不断降温的情况下,根据当前解随机出下一个解。

判断下一个解的优劣。并选择是否接受。

降温。

这个算法完成了。

值得注意的是,如果考试要用,一定要暴力多对拍几次,今天我做题对拍了半天没有问题,然而过了一会还是出现了差别。

如果代码过不了,注意调整初始温度和降温参数。程序的效率与它们是紧密相关的。精度不够的话可以试试( ext{long double.})

面对不会的题也可以得分,何乐而不为呢?

下面放一个(P1337)平衡点的代码。

题目大意就是使得距离乘以重量的和最小,这样才平衡。

我们退火找一个点,判断它与当前答案的差。这里的差别指的是我们求的(sum dist*wight)的差别。注意不同题目比较的标准不同,计算函数也不同。

这份代码我没有选择概率放弃解的写法,但依旧跑的挺快。

#include<bits/stdc++.h>
using namespace std;
typedef long double db;
const db h=0.96;
int n;
struct point{
    int x,y,w;
}p[5000];
db ansx,ansy,answ;
inline db calc(db x,db y){
    db sum=0;
    for(int i=1;i<=n;++i){
        db dx=x-p[i].x;
        db dy=y-p[i].y;
        sum+=(sqrt(dx*dx+dy*dy))*p[i].w;
    }
    return sum;
}
inline void solve(){
    db T=3000;
    db x=ansx,y=ansy;
    answ=min(answ,calc(x,y));
    while(T>1e-14){
        db qx=ansx+T*(rand()*2-RAND_MAX),qy=ansy+T*(rand()*2-RAND_MAX);
        db Ew=calc(qx,qy);
        if(Ew<answ){
            answ=Ew;
            ansx=qx;
            ansy=qy;
        }
        T*=h;
    }
}
void work(){solve();}
int main(){
    srand(time(0));
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].w);
    answ=9999999999;work();
    printf("%.3Lf %.3Lf
",ansx,ansy);
    return 0;
}
原文地址:https://www.cnblogs.com/h-lka/p/12521021.html