模拟退火

Summary

退火总结.退火其实不难...难的是怎么调参.
贡献两页提交才只有\(55pts\)的经历真是惨不忍睹.这就是调参的艰难.
主要就是这么几个参数:
\(T_0,T,d,T_e\).
分别是初温,当前温度,降温系数,终止温度.
主要就说一下降温系数叭.
这个东西,呃,很玄乎,你改个\(0.\)几它可能人就没了,也可能就直接\(AC\)了.
那么退火这个东西,实质上是个随机化算法,是对爬山算法的优化.
爬山算法的流程就是随机状态,得到该状态解,更优则转移,否则跳过.
这样很显然如果答案是多峰函数,很可能就死在一个峰出不来了.
而退火,则有效地避免了这种情况.
它的工作流程也是,随机状态,得到该状态解,更优则转移,否则以一定的概率去接受这个状态得到的不优解.
为什么会有这个一定概率接受呢?
因为这个不优解有可能来自于另一个峰的某个位置,而我们当前峰的峰顶很可能并不如另一个峰的峰顶要优.于是我们选择接受这个不优解,实际上是给了自己一个机会,不让自己死在一个峰上.
那重点来了!这个一定概率是个啥啊?
你想啊,退火是个玄学随机算法,那么不如这个概率也随个机算了.
于是我们一般取这个概率\(P_c=rand()/RAND\_MAX\).
而这个概率是在这里了,那我们以什么为标准取和这个概率评判呢?
\(e\)的能量差次幂和当前温度的商作为标准比对,大于则接受否则拒绝(也有可能改变),这个能量差\(\Delta\),要小于等于\(0\).
为什么呢?显然,\(P_c\le 1\)\(e=2.718281828...\),若\(\Delta>0\),显然我们就只能一直接受或一直拒绝,具体接受还是拒绝取决于你前面的决策.
至于那个著名的退火图...
图挂了
啊,清爽!唉...好像忘了说降温系数咋用了...
一句话,每次降温用.当前温度乘上降温系数.
显然,温度越低,就越稳定.(图也能说明问题)
通用板子:

for (int T = T0 ; T >= Te ; T *= d) {
    // rand 一个合法状态
    // Example:A permutation.
    int l = rand () % n + 1 , r = rand () % n + 1 ;
    // 得到该状态
    swap ( v[l] , v[r] ) ;
    // 得出该状态的解
    // Example:
    int res = get_ans () ;
    // 判断是否比当前解更优,若是,直接继承
    // Example:
    if ( ans < res ) ans = res ;
    // 否则以一定概率接受该不优解
    else if ( exp ( ( res - ans ) / T ) * RAND_MAX < rand () )
        // 撤回操作.
        swap ( v[l] , v[r] ) ;
}

重点就是快速得到某个状态的解,所以通常退火应用于\(n\)比较小,或者有其他美妙的性质可以使你快速得到一个解的情况.(所以最优性状压题经常被退过去,但也不是所有的最优性状压都能被退过去)
例题的话,这个叭:
[TJOI2010]分金币
代码的话,也有:

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
#define db double

using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;

template < class T >
    inline T read () {
        T x = 0 , f = 1 ; char ch = getchar () ;
        while ( ch < '0' || ch > '9' ) {
            if ( ch == '-' ) f = - 1 ;
            ch = getchar () ;
        }
       while ( ch >= '0' && ch <= '9' ) {
            x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
            ch = getchar () ;
       }
   return f * x ;
}

const int N = 35 ;

int Tot , n , v[N] , ans ;

inline int mabs (int x) { return x < 0 ? - x : x ; }

inline int initial () {
    int mid = ( n + 1 ) >> 1 ;
    int sum1 = 0 , sum2 = 0 ;
    rep ( i , 1 , mid ) sum1 += v[i] ;
    rep ( i , mid + 1 , n ) sum2 += v[i] ;
    return mabs ( sum1 - sum2 ) ;
}

signed main (int argc , char * argv[]) {
    srand ( time ( NULL ) ) ;
    srand ( rand () ^ rand () ) ;
    Tot = rint () ; while ( Tot -- ) {
        n = rint () ; ans = 0x7f7f7f7f ;
        rep ( i , 1 , n ) v[i] = rint () ;
        rep ( i , 1 , 100 ) {
            db T = 5e4 ;
            while ( T >= 1e-10 ) {
                int l = rand () % n + 1 , r = rand () % n + 1 ;
                swap ( v[l] , v[r] ) ; int res = initial () ;
                if ( ans > res ) ans = res ;
                else if ( exp ( ( ans - res ) / T ) * RAND_MAX < (db) rand () )
                    swap ( v[l] , v[r] ) ;
                T *= 0.98 ;
            }
        }
        printf ("%lld\n" , ans ) ;
    }
    system ("pause") ; return 0 ;
}

什么?\(n\le500\)?退就完了!

posted @ 2019-09-25 19:46  Phecda  阅读(248)  评论(0编辑  收藏  举报

Contact with me