随机化
随机化选讲
一.模拟退火
开讲
模拟退火是用来解决能抽象成函数而且要取最值的问题的,以下举两个例子:
-
给出二维平面的一些点,让你找出一个点,使这个点到所有给出的点的距离和最小/最大值最小/最小值最大等。
-
给出一个序列,让你自行决定序列的顺序,使得这个序列的权值符合一定限制或取最值。
对于解决这些问题,我们需要使用万能的模拟退火,这里我先讲一下模板
基本思想(看不懂可以直接看省流)
- 首先,明确一点物理上的芝士,温度(内能)越大,分子运动速度越剧烈,随着温度(内能)降低,分子运动逐渐趋于稳定,而且分子是无规律运动。
- 根据这一点,我们可以窥探到模拟退火的本质,我们把一个状态(通常二维平面的一个点或序列的一个排列)抽象成 \(x\) ,它对应的答案抽象成 \(y\) 。
- 首先,我们需要一个较高的初温 \(T\) ,以及一个点模拟分子,然后每次循环随机一个状态模拟分子随机运动(假如是实数范围的话通常与 \(T\) 有关,因为温度越高分子运动越剧烈),假如答案更优我们就接受这个状态,否则以一定的概率( \(T\) 越高概率越大,温度越高分子运动越剧烈)接受劣解。感觉说的有点抽象,请看演示。
省流:先抽象成函数,在图像上随机蹦,更优就取,否则温度高接受劣解的概率高,而且蹦的远
关于接受劣解
我们通常用数学上的 \(exp\) 函数来求概率,下面给出 \(exp\) 函数图像
下面是代码。
if(exp(del/T)*RAND_MAX > rand()) /*接受劣解*/
其中 RAND_MAX
为一个常量,它的值为 32767 ,即 rand()
的最大值。
观察图像我们发现当 \(x<0\) 时,\(y < 1\) ,假如 \(\frac{del}{t} \ge 0\) ,那么对应的 \(y\ge1\) ,这时 exp(del/T)*RAND_MAX
必然是大于 rand()
的,所以我们要保证 $ \frac{del}{t}<0 $ 。
del
为当前状态对应的答案和记录的最优答案的差值,有两种情况:
- 要求最小值,所以
del
为正,del
要取负。 - 要求最大值,所以
del
为负,不用管。
关于如何退火
模拟退火时我们有三个参数:初始 \(T_0\) ,降温系数 \(d\) ,终止温度 \(T_k\) 。其中 $T_0 $ 是一个比较大的数,\(d\) 是一个非常接近 \(1\) 但是小于 \(1\) 的数,\(T_k\) 是一个接近 \(0\) 的正数。
首先让温度 \(T=T_0\) ,然后按照上述步骤进行一次转移尝试,再让 \(T=d\times T\) 。当 \(T<T_k\) 时模拟退火过程结束,当前最优解即为最终的最优解。
代码
void sa()
{
double T = 1111;
while(T > 1e-9)
{
int x = nx+(rand()*2-RAND_MAX)*T,y = ny+(rand()*2-RAND_MAX)*T;//随机s
int now = W(),del = now-ans;//计算当前答案和与记录最优解的差值
if (del >= 0) ansx = x,ansy = y,ans = now;
else if (exp(-del/T)*RAND_MAX > rand()) nx = x,ny = y;//接受劣解
T *= d;//d为降温系数
}
}
巨大多例题
[TJOI2010] 分金币 随机交换两个金币
RUNAWAY - Run Away 随机移动点
[JSOI2016] 炸弹攻击1 随机移动点,注意生成状态的答案为 0 时不接受劣解
A Star not a Tree? 随机移动点
Haywire 随机交换两只奶牛
[JSOI2004] 平衡点 / 吊打XXX 随机移动点
[SCOI2008] 城堡 随机交换两个城市
Coloring 随机交换两个格子的颜色
[HAOI2006] 均分数据 随机交换两个数跑 \(dp\)
[CEOI2004] 锯木厂选址 随机选两个点作为锯木厂,要推式子降低计算答案的复杂度
偷上网 虽然要可行解,但是可以直接求最大解判断,生成的点记得取模
外太空旅行 随机交换
[POI2008] POD-Subdivision of Kingdom 随机交换
[USACO12MAR] Cows in a Skyscraper G 随机交换,也可以试试 shuffle
关路灯 降温系数要小
[WC2018] 通道 这是爬山
[蓝桥杯 2015 国 B] 居民集会 还没调出来呢,写的很烦人,不写了
Mike and distribution 退火因为有两个限制不是很好退,所以直接 shuffle
[POI2004] PRZ 随便退
吃奶酪 旅行商问题,退
部落卫队 退
Leaving the Bar \(n\) 有点大,shuflle
Graph Reconstruction shuffle
,相邻两个点连
[HNOI2011] 任务调度 这个有点恶心,别碰
售货员的难题 旅 ~ 行 ~ 商
邦邦的大合唱站队 The last
退火的两种写法
第一种
将随机到的答案跟最优解比较
void sa()
{
double T = 1111;
while(T > 1e-9)
{
int x = nx+(rand()*2-RAND_MAX)*T,y = ny+(rand()*2-RAND_MAX)*T;
int now = W(),del = now-ans;
if (del >= 0) ansx = x,ansy = y,ans = now;
else if (exp(-del/T)*RAND_MAX > rand()) nx = x,ny = y;
T *= d;
}
}
第二种
跟目前接受的解比较,这个更不容易被卡在一个局部最优解,但是也容易跑偏
void sa()
{
double T = 1111;
while(T > 1e-9)
{
int x = nx+(rand()*2-RAND_MAX)*T,y = ny+(rand()*2-RAND_MAX)*T;
int now = W(),del = now-nans;
else if (del < 0 || exp(-del/T)*RAND_MAX > rand()) nx = x,ny = y;
ans = min(ans,now);
T *= d;
}
}
结论
主要是个人经验,借鉴一下就好。
具体情况还是要具体分析。
-
关于退火能解决的问题一般具备的特点
- \(n\) 通常在几十左右
- 能被抽象为较为平滑函数
- 求极值问题或能判断优劣解的可行解问题(这个题就无法判断优劣解)
-
什么时候用
shuffle
- 跟序列关系有关,整数范围。
- 可行解,因为最优解跑
shuffle
不是找死? - \(n\) 较大,这样依赖退火生成的排列就会比较集中,不够随机。或者你可以更改退火里随机状态的方式(其实就是变得更随机)
- 无法判断优劣解的题
-
关于我的参数
- 实数范围的时候终止温度 \(T_k\) 是 1e-11 ,整数范围 1e-7
- 初温 \(T\) 几千几万都无所谓,能把所有的状态都覆盖掉就行。假如
del
很大的话为了方便接受劣解可以多除一个数 - 降温系数实数范围 0.996-0.998 差不多了,有的题比较极端,要 0.999,自行调整。整数范围 0.99-0.998
-
如何调参
这里只说一下各个参数所造成的影响,我觉得理解了这个就会自己调参了
- 初温 \(T\) ,活动范围的大小,接受劣解的概率,退火的次数
- 末温 \(T_k\) ,与正确答案的接近程度,退火的次数
- 降温系数 \(down\) ,退火的次数,接受劣解的概率
关于不连续函数
容易发现这里你随机序列的话很容易导致无法把灯全部关掉,这样的问题无法用退火解决。