随机数
思路1
在C++中,生成随机数的函数为rand()
,返回一个不大于RAND_MAX
的非负数。但是,如果生成随机数的种子和生成的次数相同,那么生成的随机数相同,因此rand()
只能生成伪随机数。为了尽量得到真随机数,每次应该取不同的种子,由于time(NULL)
返回从1970.1.1/00:00到现在的秒数,可以采用srand(time(NULL))
。因为程序运行非常快,两次取随机数的间隔可能不超过111秒,所以在生成随机数的函数中设置种子有可能返回同一个值,事实上每次运行程序设置一次种子就够了。为了生成区间[a,b][a, b][a,b]的随机数,可以简单地取rand() % (b-a+1) + a
。各随机数对应区间如下:
随机数 | 区间 |
---|---|
rand() % (b-a+1) | [0,b−a][0, b-a][0,b−a] |
rand() % (b-a+1) + a | [a,b][a, b][a,b] |
模板1
#include <cstdio>
#include <cstdlib>
#include <ctime>
typedef long long LL;
bool tag = false;
/**
* @return: the random integer in [a, b]
*/
LL getRandInt(LL a, LL b) {
if (!tag) {
srand(time(NULL));
tag = true;
}
return rand() % (b-a+1) + a;
}
思路2
然而,按照思路1的方法得到[a,b][a, b][a,b]中每一个数的概率并不相等。举个例子,设rand()
返回的最大值RAND_MAX
为MMM,那么对于区间[0,M−1][0, M-1][0,M−1],若rand()
返回[0,M−1][0, M-1][0,M−1]中的数,则生成对应的随机数;若rand()
返回MMM,则生成000,因此生成000的概率是其它随机数的两倍。为了解决这个问题,引入生成区间[0,1)[0, 1)[0,1)中随机浮点数的函数getRandDouble()
,返回rand() / (RAND_MAX + 1.0)
,然后再返回floor(getRandDouble() * (b-a+1)) + a
。各随机数对应区间如下:
随机数 | 区间 |
---|---|
getRandDouble() | [0,1)[0, 1)[0,1) |
getRandDouble() * (b-a+1) | [0,b−a+1)[0, b-a+1)[0,b−a+1) |
floor(getRandDouble() * (b-a+1)) | [0,b−a][0, b-a][0,b−a] |
floor(getRandDouble() * (b-a+1)) + a | [a,b][a, b][a,b] |
模板2
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
typedef long long LL;
bool tag = false;
/**
* @return: the random number in [a, b)
*/
double getRandDouble() {
if (!tag) {
srand(time(NULL));
tag = true;
}
return rand() / (RAND_MAX + 1.0);
}
/**
* @return: the random integer in [a, b]
*/
LL getRandInt(LL a, LL b) {
if (!tag) {
srand(time(NULL));
tag = true;
}
return floor(getRandDouble() * (b-a+1)) + a;
}
思路3
实际上,getRandDouble()
并不能返回区间[0,1)[0, 1)[0,1)中的任意一个浮点数,因此依靠getRandDouble()
生成的随机数仍然不是真随机数。举个例子,设rand()
返回的最大值RAND_MAX
为MMM,那么对于区间[0,M+1][0, M+1][0,M+1],rand()
最多只能返回M+1M+1M+1个数,因此至少有一个数生成的概率为000。接下来考虑扩大rand()rand()rand()返回随机数的范围,如果取两个随机数为一对,那么从(0,0)(0, 0)(0,0)到(M,M)(M, M)(M,M)共有(M+1)×(M+1)(M+1) \times (M+1)(M+1)×(M+1)对,也就是说,随机数rand() * (RAND_MAX+1) + rand()
可以表示区间[0,(M+1)×(M+1)−1][0, (M+1) \times (M+1) - 1][0,(M+1)×(M+1)−1],因此只要取一个以rand()
为数位的(M+1)(M+1)(M+1)进制随机数就可以表示任意大的区间范围。为了使生成每一个数的概率相等,从最大区间截取一部分使得区间长度为(b−a+1)(b-a+1)(b−a+1)的倍数,再对生成的随机数按思路1处理即可。
模板3
#include <cstdio>
#include <cstdlib>
#include <ctime>
typedef long long LL;
bool tag = false;
/**
* @return: the random integer in [a, b]
*/
LL getRandInt(LL a, LL b) {
if (!tag) {
srand(time(NULL));
tag = true;
}
LL len = b-a+1;
LL tmp = RAND_MAX + 1;
LL rk = 1;
while (tmp < len) {
tmp *= RAND_MAX + 1;
++rk;
}
tmp = tmp / len * len; // tmp = k * len, k > 0
LL ans;
while (true) {
ans = 0;
for (int i = 0; i < rk; ++i) {
ans = ans * (RAND_MAX + 1) + rand();
}
if (ans < tmp) break; // [0, tmp-1]
}
ans = ans % len + a; // [0, len-1] + a
return ans;
}