1. 程式人生 > 其它 >模擬退火筆記(詳細)

模擬退火筆記(詳細)

已知我們從黃色出發,找最小值。 貪心策略當然是一直往函式大小減小的地方偏移——但是,萬一不是單峰呢?我們會陷入如圖的藍色中無法自拔。 肯能你會想到:隨機找一個點出發,然後貪心找最小值?多隨機幾遍,然後求全域性最小值? 你會發現複雜度暴增!!!!!!!!——所以如何處理這種問題呢? 模擬退火:啊,對對對~

彆著急,乾貨在最後面!!!(本文用c++實現)

很多人都學過貪心,但是貪心在一些情況並不適用,比如:

已知我們從黃色出發,找最小值。

貪心策略當然是一直往函式大小減小的地方偏移——但是,萬一不是單峰呢?我們會陷入如圖的藍色中無法自拔。

肯能你會想到:隨機找一個點出發,然後貪心找最小值?多隨機幾遍,然後求全域性最小值?

你會發現複雜度暴增!!!!!!!!——所以如何處理這種問題呢?

模擬退火:啊,對對對~

是的!模擬退火就是一種類似於隨機化貪心的一個演算法,在OI界也小有名氣(冥器)!(如題[NOIP2021] 方差

原理圖:

如圖:在物理應用中分子排布可能是紊亂的,如果我們將它升溫然後緩慢降溫,就可以生成完美的晶形!

所以我們立刻(啊,對對對~)能設定模擬退火的引數:

1.初始溫度 T (1000-7000)

2.末尾溫度 P(1e-5~1e-15)

3.降溫係數 K (0.91~0.9975)

4.狀態空間(被降溫物體) S

5.當前能量 E ( new )

6.全域性能量 E ( old )

一:Metropolis準則- 以概率接受新狀態:

這就是物理(化學)方面類似的推論——一定概率的更新。

什麼意思呢?

我們已知:當前能量 E ( new ) , 全域性能量 E ( old ),那麼我們的目標是什麼,不就是減少目前的能量嗎?

所以:噹噹前能量少於全域性能量(即更新前的能量),那麼我們有概率為 1 的更新概率;

   噹噹前能量大於全域性能量(即更新前的能量),那麼我們有概率為 exp( - (E( new )-E( old ))/T) 的更新概率 ( T為當前溫度) ;

注意:有時候也不一定以以上方式更新,這只是比較妥的做法,概率方面是可以自己定的,但是一定以當前能量與全域性能量的關係來設定的。(除非直接暴力的隨機演算法)

二:

那麼怎麼生成新的當前溫度呢?,以生成小數為例:

當前將更新溫度=全域性溫度+(rand()*2-RAND_MAX)*t;
if(不在狀態空間內){
    當前將更新溫度=fmod(當前將更新溫度,狀態空間大小)
}

即:在當前狀態的鄰域結構內以一定概率方式(均勻分佈、正態分佈、指數分佈等)產生。

三:溫度更新函式

若固定每一溫度,演算法均計算至平穩分佈,然後下降溫度,則稱為時齊演算法;

若無需各溫度下演算法均達到平穩分佈,但溫度需按一定速率下降,則稱為非時齊演算法。

本人用的:

T*=K;

四:內迴圈終止準則

本人使用的:

(t>1e-15)//可以改大一點

其他常用方法:

(1)設定終止溫度的閾值。

(2)設定外迴圈迭代次數。

(3)演算法搜尋到的最優值連續若干步保持不變。

(4)概率分析方法。

五:實現流程圖:

注意:以下是我自己總結的退火口訣:

初始溫度小心設(1000-3000),又粗又大wa一臉

多次sa更保險,忘了卡時直接T[if((double)clock()/CLOCKS_PER_SEC>=0.993)]

退火係數大膽設,不過0.9975會很厄

全域性、狀態不一樣,全域性必須菊部優

百年騙分一場空,不開srand見祖宗

退火需謹慎,退火不規範,靈封兩行淚

然後是[NOIP2021]方差的實現(玄學萬歲):

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+10;
const double dw=0.9975;
int a[N],n,c[N];
long long ans;
bool cmp(int a,int b){
    return a>b;
}
LL en(){
    LL em=0;
    LL ranss=0;
    for(int i=2;i<=n;i++){
        a[i]=a[i-1]+c[i];
    }
    for(int i=1;i<=n;i++){
        em+=(long long)a[i]*a[i];
    }em=(long long)em*n;
    for(int i=1;i<=n;i++){
        ranss=(long long)ranss+a[i];
    }ranss=(long long)ranss*ranss;
    return (long long)(em-ranss);
}
void sa(){
    double t=1000;
    while(t>1e-15){
        if((double)clock()/CLOCKS_PER_SEC>=0.993){
            cout<<ans;
            exit(0);
        }
        int x=rand()%(n-1)+2,y=rand()%(n-1)+2;
        while(x==y)x=rand()%(n-1)+2;
        swap(c[x],c[y]);
        LL m=en(),dt=ans-m;
        if(dt>0){
            ans=m;
        }else if((double)rand()>=(double)RAND_MAX*(double)exp((double)dt/t)){
            swap(c[x],c[y]);
        }
        t*=dw;
    }
}
int main(){
    srand((unsigned)time(0));
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        c[i]=a[i]-a[i-1];
    }
    sort(c+2,c+n/2+1,cmp);
    sort(c+n/2+1,c+n+1);
    ans=en();
    while(1)sa();
}