模拟退火学习笔记

【前言】

好文章先放这:OI wiki - 模拟退火

模拟退火是一个著名的 玄学 算法,理论上来说 只要欧气爆棚就 能解决所有最优化问题。

【主要思想】

【随机数】

一开始就是玄学

模拟退火本质上依赖的是随机化,所以随机数的生成是必须的。

  1. srand(time(0)),就是个随机数种子,放在主函数开头就行了。

  2. DB Rand1(){return (DB) rand() / RAND_MAX;},用于生成一个 \(< 1\) 的随机小数,

  3. int Rand2(int x){return rand() % x + 1;},用于生成一个在 \([1,x]\) 范围内的数。

【模拟退火】

首先得有一个初始温度 \(t\),控制的是模拟退火次数,理论上来说越多越好,但你肯定不能超时。

然后每次退火,令 \(t\times {\rm down}\),其中 \(\rm down\) 是一个常数,通常取 \([0.990\sim 0.999]\),目的是模拟慢慢退火的过程。

然后在每次退火的过程中,随机地扰动,得到一个新状态。

值得一提的是,随机的扰动的增量范围通常与 \(t\) 成正比,也就是说温度越低,扰动幅度越小。

然后用一句话概括:如果新状态的解更优则修改答案,否则以一定概率接受新状态。

至于概率是什么,当然也是随机了。

根据 玄学 科学计算,代码一般长这样:if(exp(-delta) / t < Rand1()) ...,其中 ... 表示撤销改动。

【可能的优化】

  1. 退火结束后再跑个 \(1000\) 次左右,有更大的概率得到答案。
  2. 为了 保证正确率 并且 不超时,可以选择卡时:while ((double)clock()/CLOCKS_PER_SEC < 0.80)
  3. 调参很关键,一般可以根据大样例手动二分。

然后说一点个人习惯:

  1. \(t\)\(10^5\) 左右。
  2. \(\rm eps\)\(1^{-10}\) 左右。
  3. \(\rm down\)\(0.997\) 左右。

【适用情况】

  1. 你实在想不到正解。
  2. 是最优化问题,而且数据范围通常比较小(便于计算代价)。
  3. 题目看上去就很玄学

【代码实现】

例题:平衡点 / 吊打XXX

求一个坐标 \((x,y)\) 使 \(\sum_{i=1}^n dis((x_i,y_i),(x,y))\times w_i\) 最小。

看上去就很模拟退火,直接上代码吧。

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<cmath>
using namespace std;

const int N = 1010;
int n, x[N], y[N], w[N];
double ansx, ansy, dis;

int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
    return x*f;
}

double Rand(){return (double) rand() / RAND_MAX;}
double calc(double xx, double yy){
    double res = 0.0;
    for(int i = 1; i <= n; i ++){
        double dx = xx - x[i], dy = yy - y[i];
        res += sqrt(dx * dx + dy * dy) * w[i];
    }
    if(res < dis) dis = res, ansx = xx, ansy = yy;
    return res;
}
void work(){
    double t = 100000;
    double nowx = ansx, nowy = ansy;
    while(t > 0.001){
        double nxtx = nowx + t * (Rand() * 2 - 1);
        double nxty = nowy + t * (Rand() * 2 - 1);
        double delta = calc(nxtx, nxty) - calc(nowx, nowy);
        if(exp(-delta / t) > Rand()) nowx = nxtx, nowy = nxty;
        t *= 0.997;
    }
    for(int i = 1; i <= 1000; i ++){
        double nxtx = ansx + t * (Rand() * 2 - 1);
        double nxty = ansy + t * (Rand() * 2 - 1);
        calc(nxtx, nxty);
    }
}

int main(){
    srand(time(0));
    n = read();
    for(int i = 1; i <= n; i ++){
        x[i] = read(), y[i] = read(), w[i] = read();
        ansx += x[i], ansy += y[i];
    }
    ansx /= n, ansy /= n, dis = calc(ansx, ansy);
    work();
    printf("%.3lf %.3lf\n", ansx, ansy);
    return 0;
}

由于算法本身的不稳定性,或许你要交很多遍才能过。

【简单例题】

Haywire

\(n\) 个数的排列顺序,使得有关联的数之间的距离差距和最小,一个数与三个数有关系,关系是相互的。

每次随机交换两个数的位置即可。

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<cmath>
using namespace std;
typedef double DB;

const DB down = 0.997;
int n, ans, pos[15], a[15][5];

int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
    return x*f;
}

DB Rand1(){return (DB) rand() / RAND_MAX;}
int Rand2(int x){return rand() % x + 1;}

int calc(){
    int rec = 0;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= 3; j ++)
            rec += abs(pos[i] - pos[a[i][j]]);
    rec >>= 1;
    ans = min(ans, rec);
    return rec;
}

void work(){
    DB t = 100000, eps = 1e-16;
    int now = ans;
    while(t > eps){
        int x = Rand2(n), y = Rand2(n);
        swap(pos[x], pos[y]);
        int nxt = calc();
        int delta = nxt - now;
        if(delta < 0) now = nxt;
        else if(exp(-delta) / t < Rand1()) swap(pos[x], pos[y]);
        t *= down;
    }
    for(int i = 1; i <= 1000; i ++){
        int x = Rand2(n);
        int y = Rand2(n);
        swap(pos[x], pos[y]);
        calc();
    }
}

int main(){
    srand(time(0));
    n = read();
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= 3; j ++) a[i][j] = read();
    for(int i = 1; i <= n; i ++)
        pos[i] = i;
    ans = calc();
    work();
    printf("%d\n", ans);
    return 0;
}

还有些简单习题:

  1. 均分数据,然而笔者自己交了无数次也没有过。
  2. 炸弹攻击

完结撒花。

posted @ 2021-04-01 21:36  LPF'sBlog  阅读(107)  评论(0编辑  收藏  举报