闲话 22.9.29

闲话

发现很多人都会很用心来写闲话。
gtm数了数muel的闲话 一共514字
是不是我写个114字就达到标准了呢?

gtm在闲话里不断记录他人的话语。
似乎闲话就是这种东西,记录你当时想写的东西

某人提醒我说有相册这种东西
似乎是的 我保存了很多人的很多相册
所以不用担心图荒了
但我更喜欢在blog里放歌词
所以和我无关

教室の廃材が宙に浮かぶ

やっぱどうしたって嫌なもんは

嫌なんだろうなきっと

ひとりぼっちを選べない私の

馬鹿げたモノローグ

……

模拟退火(SA)

今天lyin退火切了T4
我认为随机化是很好的东西
于是来学一学

模拟退火是什么?

随机化。
我们着眼的是工业上的退火程序。由于退火的规律引入了更多随机因素,那么我们得到最优解的概率会大大增加。于是我们可以将目标函数作为能量函数,模拟这个程序。

怎么退火?

首先要有一个当前温度 \(T_0\),然后再来一个终止温度 \(T_e\) ,接着再给一个降温系数 \(d\)。其中 \(T_0\) 是一个很大的数(一般取 \(10^9\)\(10^{12}\) ),\(T_e\) 是一个较小的数(一般取 \(10^{-12}\)\(10^{-16}\),当然你取 \(10^{-66}\) 也没人管你),降温系数是一个接近 \(1\) 小于 \(1\) 的数。

参数部分很玄学,请自行尝试更多的可能性。

假设我们当前有了一个状态,它的解是 \(S_0\)。然后我们随机更改这个状态,得到新的解 \(S_1\)。假如 \(S_1\) 优于 \(S_0\) 则接受这个新的答案,若 \(S_1\) 劣于 \(S_0\) 则以概率接受这个答案。后半部分很重要,是退火的关键。
假设劣解与优解的差值为 \(\Delta S>0\),当前温度为 \(T\),则我们接受这个解的概率是 \(\large e^{\frac {-\Delta S} T}\)。容易发现这个概率的取值为 \((0,1)\)。由于指数上 \(\frac 1T\) 的存在,当温度很高时劣解的劣势会被减小,我们就有更多的可能跳出当前次优解,并在温度减小的过程中逐渐稳定,最终以很大的概率到达最优位置。

我们称这样的随机更改为一次转移。每次转移后执行 \(T_0 = d\times T_0\),当 \(T_0 < T_e\) 时结束退火,反之继续转移。
通常维护所有转移后得到解中最优解作为答案。

保证能切吗?

不保证。

但是有很多技巧可以尝试。玄学

1. 玄学调参

一眼扫过去我们能发现很多数值,像是 \(T_0,d\) 啥的。随便改改数,拟合一下大样例是很好的选择。
注意不要自己落入次优解。

2. 分段模拟退火

把值域分开,每段单独跑一次退火。
如果这个函数的峰很多那可以优化求解,但是如果你在跑单峰那还是别麻烦自己了。

3. 多跑几次

跑一遍退火得到当前解,再把它作为初始解扔到退火函数里再跑一次。
一般跑个三五次就够了。但如果你觉得不过瘾——

4. 卡时

暴力做法必备。
使用 clock() 函数返回程序运行时间,如果再跑一次退火就超时就直接输出答案并结束程序。

这里假设你的退火函数叫 SA()

do { 
	SA() ;
} while(1.0 * clock() / CLOCKS_PER_SEC < 0.95); // 0.95 可以换成时间限制减去0.1或0.05,具体由跑一次退火的时间决定。

实例

by LYinMX

const double d = 0.99, T0 = 1e9, Te = 1e-12;
double res; // 答案

mt19937 mt( (ull)(&N) );
int Rand ( int l, int r ) { return uniform_int_distribution<>(l,r)(mt); }
db Randdb ( db l, db r ) { return uniform_real_distribution<>(l,r)(mt); }

void SA () {
    { // 初始化状态
        for( int i = 1; i <= n; ++i ) arr[i] = Randdb(0,1), arr[0] += arr[i];
        for( int i = 1; i <= n; ++i ) arr[i] = arr[i] / arr[0] * x;
    }
	double t = T0, now = 0;
	for( int i = 1; i <= n; ++i ) for( auto it : son[i] ) now += arr[i] * arr[it] / 2; // 初始化答案
	while( t > Te )
	{
		{ // 计算当前解
            int x = Rand(1,n), y = Rand(1,n); 
            double e = arr[x] * Randdb( Lim( t * 1e-6 ), Lim(t) ), sum = now;
            for( auto it : son[x] ) sum -= e * arr[it]; arr[x] -= e; 
            for( auto it : son[y] ) sum += e * arr[it]; arr[y] += e;
        }
        
		double delta = sum - now; 
        res = max( res, sum );
		if( Randdb(0,1) <= exp(delta/t) ) now += delta; // 当 delta > 0 时必定接受解 反之按随机概率接受解
        else arr[x] += e, arr[y] -= e; // 不接受

		t *= d; // 冷却
	}
}
posted @ 2022-09-29 17:36  joke3579  阅读(104)  评论(6编辑  收藏  举报