解析·玄學 模擬退火

迷失の風之旅人發表於2018-10-23

讓火焰淨化一切!
火元素領主拉格納羅斯

模擬退火演算法(Simulate Anneal,SA)是一種通用概率演演算法,用來在一個大的搜尋空間內找尋命題的最優解。模擬退火是由S.Kirkpatrick, C.D.Gelatt和M.P.Vecchi在1983年所發明的。在1985年也獨立發明此演演算法。
模擬退火的出發點是基於物理中固體物質的退火過程與一般組合優化問題之間的相似性。模擬退火演算法是一種通用的優化演算法,其物理退火過程由加溫過程、等溫過程、冷卻過程這三部分組成。
摘自百度百科

通俗地來說,模擬退火是一種用於在方案數極大的情況下求取最優解的演算法。模擬退火的實現和物理中的金屬退火流程是一樣的。物理上我們先將物體加熱,再慢慢冷卻。在OI中,我們則是先隨機求取多個解,如果當前解比之前的解更優則選取它,否則我們按照一定的概率來判斷是否選取。設這個新的解與最優解的差為(Delta E),溫度為(T)(k)為一個隨機數,那麼這個概率為:[ e^{frac{Delta E}{kT}} ]
模擬退火中一共有三個引數,初始溫度(T_0),降溫函式(Delta T)和結束溫度(T_t)。初始溫度常設為(frac{n}{2}),因為平均值更容易接近正解。降溫函式常取(T=a*T),其中(a)是一個接近於1的數。當(a)與1越接近時,我們運算的迭代次數就會越多。最終溫度(T_t)則是一個接近於0的數,同樣的,當(T_t)離0越近時運算次數就會越多。

例題

給定一個序列(a_1,a_2,a_3…a_n),求其中(a_1,a_2,…a_j)的最大值。

樣例

樣例輸入
17
6 25 -130 4 36 -11 -75 35 64 98 -1 0 27 -173 166 181 24
樣例輸出
276

很顯然,如果用常規思路的話,我根本不會做。
這時讓我們來考慮一下模擬退火(SA)
直接貼出程式碼

#include<bits/stdc++.h>
#define maxn 100000
#define maxtime 0.8//取接近1的小數即可。越接近1執行準確度越高,但同時也越容易超時 
using namespace std;
inline char get(){
    static char buf[30],*p1=buf,*p2=buf;
    return p1==p2 && (p2=(p1=buf)+fread(buf,1,30,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    register char c=get();register int f=1,_=0;
    while(c>`9` || c<`0`)f=(c==`-`)?-1:1,c=get();
    while(c<=`9` && c>=`0`)_=(_<<3)+(_<<1)+(c^48),c=get();
    return _*f;
}
int a[maxn];
int n;
int ans=0;
int out(int j){
    int now=0;
    for(register int i=1;i<=j;i++)now+=a[i];
    //cout<<now<<endl;
    return now;
}
void SA(){
    int nowans=ans;
    double T=2000;//T表示溫度 
    while(T>1e-6){
        int j=0;
        while(!j)j=rand()%n+1;//rand()%(n-m+1)+m;<------生成[m,n]之間的隨機數
        int now=out(j);//輸出交換之後的新解 
        //cout<<now<<endl;
        int pd=now-ans;//<----------關鍵部分,判斷當前值是否接受 
        if(now>ans)ans=now;
        else if(exp(-pd/T)*RAND_MAX>rand())nowans=now;
        T*=pd*0.78;
    }
}
inline void solve(){
    ans=0;
    while( (double)clock() / CLOCKS_PER_SEC < maxtime )SA();//卡執行時間 
}
int main(){
    //ps:不要看程式碼執行小資料耗時也巨大,那是因為執行時間被鎖定為接近1s 
    freopen("SA.txt","r",stdin);
    srand(rand());srand(rand());//玄學生成隨機數 
    n=read();
    for(register int i=1;i<=n;i++)a[i]=read();
    solve();
    printf("%d
",ans);
}

相關文章