随机化相关

随机函数

rand

生成伪随机数,头文件 #include<cstdlib> ,随机种子 srand(time(0))

返回一个 [0,RAND_MAX] 中的随机非负整数,其中 RAND_MAX 在 Linux 下等于 \(2^{31}-1\) ,在 Windows 下等于 \(2^{15}-1\)

注意 rand 的底层实现是 \(int\) 的,所以在 Linux 下不能左移!会爆 \(int\) 变成负数的!

在 Windows 下当需要生成的数不小于 \(2^{15}\) 时建议使用 (rand()<<15|rand()) 来生成更大的随机数

注意对于生成指定范围内的随机数,rand()%n 不保证均匀性,因为 [0,n) 中的每个数在 0\%n,1\%n,...,RAND\_MAX\%n 中出现的次数不一定相同

例:

	srand(time(0));
	int x=rand();

以下均为 C++11 标准下,头文件为 #include<random>

mt19937

随机数生成类,效果同 rand() ,范围同 unsigned int ,随机种子 mt19937 myrand(time(0))

相似的是 mt19937_64,使用方式同 mt19937,区别在于范围扩大到了 unsigned long long

例:

#include<ctime>
#include<random>
#define int long long
using namespace std;

signed main(){
    mt19937 myrand(time(0));
    int x=myrand();
    printf("%lld\n",x);
}

minstd_rand0

线性同余算法

不会

random_shuffle

随机打乱指定序列,头文件 #include<algorithm>

区间左闭右开

内部随机数生成器默认 rand(),也可自定义。random_shuffle(first, last)random_shuffle(first, last, myrand)

已于 C++14 标准中被弃用,于 C++17 标准中被移除

类似的有 shuffle,区别在于必须自定义随机数生成器即必须写 shuffle(first, last, myrand)random_shuffle 产生的排列不是等概率选的而 shuffle 是。

random_device

基于 Linux 内核熵池,依赖于熵池中收集的周围环境的噪声资源。这是真随机数,因为周围环境是不确定的,所以在熵池耗尽前可以生成高质量随机数,但是熵池耗尽后性能会急剧下降

常用于生成 mt19937 等伪随机数种子

注意:因为是基于 Linux 内核的,所以只在 Linux 下有效,在 Windows 下是不能用的。 在 Linux 下才会调用 dev/urandom 设备,而在 Windows 下调用的是 rand_s,效果等同于一般的伪随机数

例:

#include<iostream>
#include<ctime>
#include<random>
#define int long long
using namespace std;

int n;

signed main(){
    mt19937 myrand(time(0));
    // random_device rd;
    // mt19937 gen(rd());//Linux
    mt19937 gen(time(0));//Windows or Linux
    uniform_int_distribution<> dis(1,10);
    
    cin>>n;
    for(int i=1;i<=n;i++) cout<<dis(gen)<<endl;

    return 0;
}

随机数分布

要求随机数按照一定概率出现,以下是常见的随机分布模板类

类名 注释
uniform_int_distribution 产生在一个范围上均匀分布的整数值
uniform_real_distribution 产生在一个范围上均匀分布的实数值
bernoulli_distribution 产生伯努利分布上的布尔值
binomial_distribution 产生二项分布上的整数值
negative_binomial_distribution 产生负二项分布上的整数值
geometric_distribution 产生几何分布上的整数值
poisson_distribution 产生泊松分布上的整数值
exponential_distribution 产生指数分布上的实数值
gamma_distribution 产生 \(\gamma\) 分布上的实数值
weibull_distribution 产生威布尔分布上的实数值
extreme_value_distribution 产生极值分布上的实数值
normal_distribution 产生标准正态(高斯)分布上的实数值
lognormal_distribution 产生对数正态分布上的实数值
chi_squared_distribution 产生 \(x^2\) 分布上的实数值
cauchy_distribution 产生柯西分布上的实数值
fisher_f_distribution 产生费舍尔 F 分布上的实数值
student_t_distribution 产生学生 t 分布上的实数值
discrete_distribution 产生离散分布上的随机整数
piecewise_constant_distribution 产生分布在常子区间上的实数值
piecewise_linear_distribution 产生分布在定义的子区间上的实数值

例子和上面那个一样

注意:uniform_int_distribution 的随机数的范围不是半开范围 [ ),而是 [ ],对于 uniform_real_distribution 却是半开范围 [ )

爬山算法(Hill Climbing)

是一个局部搜索算法,在规定的局部状态空间里进行搜索,得出局部最优解,用于多峰函数求最优值问题

通俗地说,就是在当前最高山峰的相邻状态中找一个更高的山爬过去,如果找不到这样的山那就认为现在所在的山已经是最高的了,就说明当前的值就是最优解

但是事实上很容易找到一个局部最优解而不是全局最优解

5QX9kd.png

比如这样,你到了蓝色的山,以为自己是最优的了,但事实上黄色的山才是最优的

解决方法:多爬几次,规定个爬山的人数,给每个人 \(rand\) 出来一个初始位置让他从那开始爬,总答案取所有人里爬到的最大值,就大大增加了成功率

例题:

P1337 [JSOI2004]平衡点 / 吊打XXX

//头文件
#define N 1010
#define M 10000
#define ri register
using namespace std;
//快读

const double eps=1e-6;
const double delta=0.5;
double n,x[N],y[N],w[N],len=M,ansx,ansy;

inline void solve(double len){
	double dx=0,dy=0;
	for(ri int i=1;i<=n;i++){
		double tmp=sqrt((x[i]-ansx)*(x[i]-ansx)+(y[i]-ansy)*(y[i]-ansy));
		if(!tmp) continue;
		dx+=w[i]/tmp*(x[i]-ansx);
		dy+=w[i]/tmp*(y[i]-ansy);
	}
	double tmp=sqrt(dx*dx+dy*dy);
	if(!tmp) return ;
	ansx+=len/tmp*dx;
	ansy+=len/tmp*dy;
}

int main(){
	read(n);
	for(ri int i=1;i<=n;i++) read(x[i]),read(y[i]),read(w[i]);
	for(double len=M;len>eps;len*=delta) solve(len);
	printf("%.3lf %.3lf\n",ansx,ansy);
	return 0;
}
/*
3
0 0 1
0 2 1
1 1 1
//
0.577 1.000
*/

模拟退火(SA)

从爬山算法可以看出,直接把局部不优的解扔了挺傻的,为了跳出这个局部最优解去找全局最优解,我们有时候要接受局部不太优的解。这就是模拟退火

退火在百度百科上的解释是这样的:

退火是一种金属热处理工艺,指的是将金属缓慢加热到一定温度,保持足够时间,然后以适宜速度冷却

引用一张 OI Wiki 上的图能挺形象的说明这个过程:

5QjXZD.gif

算法就是如果搜到的新状态的解更优就更新答案,否则以一定概率接受这个新状态

设当前温度为 \(T\),新状态(从已知状态结合一定的随机化得到的)与已知状态之间的能量差为 \(\Delta E(\Delta E\ge 0)\),则发生状态转移的概率为

\[P(\Delta E)= \left\{\begin{matrix} 1 &新状态更优\\ e^{\frac{-\Delta E}{T}} &新状态更劣 \end{matrix}\right. \]

模拟退火的参数为:初始温度 \(T_0\)(一个比较大的数),降温系数 \(d\)(一个 \(0\sim 1\) 但是较为接近 \(1\) 的数,比如 \(0.95\) 这样的),终止温度 \(T_k\)(一个 \(0\sim 1\) 但是接近 \(0\) 的数,比如 \(0.001\) 这样的)

初始时 \(T=T_0\),每按着上面那个转移一次就降一点温,把 \(T\) 更新成 \(d\times T\),当温度降到 \(T<T_k\) 时就认为已经降到常温了,当前的解就是最优解

小技巧:

  • 分块模拟退火:对于一个多多多多峰函数不好找最优解,给它按值域分块,每块跑一遍,取总的最优解
  • 给程序记个时,一直跑模拟退火,直到快 \(T\) 了就退出:while ((double)clock()/CLOCKS_PER_SEC < MAX_TIME) (其实对于挺多随机化应该都能这么搞)

例题:

P5544 [JSOI2016]炸弹攻击1 (事板子!)

在写了

粒子群优化算法(PSO)

属于群智能优化算法。模仿鸟类捕食的过程,先随机撒一堆点来模拟鸟群,鸟群在一起搜索整个区域中唯一的食物,每个鸟都知道自己离食物有多远且把信息与其他鸟共享。显然,最快找到食物的办法是搜索离食物最近的鸟附近的区域,于是所有鸟都逐渐向那附近靠拢

就像这样(图片来自网络)

粒子群优化算法

初始化鸟的位置是随机的,每个鸟有速度和方向两个属性,速度代表移动的快慢,方向代表移动的方向。每个鸟在自己应该搜的范围内找到个体最优解,然后把这个消息和其他鸟分享,从里面找最优的作为当前全局最优解。其他鸟根据这个位置调整自己的速度和方向

设个体最优解为 \(pbest_i\),全局最优解为 \(gbest_i\),当前粒子速度为 \(v_i\),当前粒子位置为 \(x_i\)\(c_1,c_2\) 是学习因子,通常为 \(2\)(别问我我也不知道为啥),\(rand()\) 表示一个 \(0\sim 1\) 的随机数,惯性因子为 \(\omega\) ,其值为非负

有三个公式:

公式一:

\[v_i=v_i+c_1\times rand()\times(pbest_i-x_i)+c_2\times rand()\times(gbest_i-x_i) \]

其中第一项为记忆项,第二项为自身认知项,第三项为群体认知项

公式二:

\[x_i=x_i+v_i \]

公式三:

\[v_i=\omega\times v_i+c_1\times rand()\times(pbest_i-x_i)+c_2\times rand()\times(gbest_i-x_i) \]

其中 \(\omega\) 是用来调整全局寻优能力和局部寻优能力的,改善了普通粒子群算法陷入局部最优的情况。\(\omega\) 越大全局寻优能力越强,局部寻优能力越弱。\(\omega\) 可以是固定值也可以动态改变,常用的动态改变惯性的方法为线性递减权值策略:

\[\omega^t=\frac{(\omega_{ini}-\omega_{end})(G_k-g)}{G_k}+\omega_{end} \]

其中 \(G_k\) 为最大迭代次数,\(\omega_{ini}\) 为初始惯性权值,\(\omega_{end}\) 为迭代至最大进化代数时的惯性权值。典型权值为 \(\omega_{ini}=0.9,\omega_{end}=0.4\)

在以上三个公式中,公式一、二为 \(PSO\) 的标准形式公式,公式二、三为标准 \(PSO\) 算法

例题:

P2571 [SCOI2010]传送带 (看了首赞题解才知道这个算法的QAQ)

在写了

参考资料:

posted @ 2021-10-14 16:50  DReamLion  阅读(137)  评论(1编辑  收藏  举报