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$,显然我们就只能一直接受或一直拒绝,具体接受还是拒绝取决于你前面的决策.
至于那个著名的退火图…
啊,清爽!唉…好像忘了说降温系数咋用了…
一句话,每次降温用.当前温度乘上降温系数.
显然,温度越低,就越稳定.(图也能说明问题)
通用板子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17for (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]分金币
代码的话,也有:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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$?退就完了!