随机数生成工具

随机数生成工具

一句话工具

mt19937 生成可控范围内的随机整数,主要用于造数据、对拍和构造压力测试。

为什么需要它

算法竞赛里写随机数,最常见的用途不是“随机算法”,而是“检查程序”。

当你写了一个高效解法,但不确定它是否正确时,可以再写一个暴力解法,然后不断随机生成小数据,让两个程序跑同一组输入。如果输出不同,就说明至少有一个程序错了。这个过程通常叫对拍。

随机数据生成器需要满足三个要求:

  • 能方便地生成指定区间内的数。
  • 每次运行的数据尽量不同,便于覆盖更多情况。
  • 必要时可以固定种子,复现某一次错误数据。

mt19937rand() 更稳定,周期更长,分布质量也更适合竞赛中的数据生成。

核心接口

模板里最常用的函数是:

cpp
        
1
int rnd(int l, int r);

它返回闭区间 [l, r] 中的一个随机整数。

典型用法:

cpp
        
1
2
int n = rnd(1, 10); int x = rnd(-100, 100);

注意这里的右端点 r 是能取到的,这一点和很多半开区间写法不同。

使用步骤

  1. 先确定题目的数据规模,把随机范围设小一点,便于暴力解法跑得动。
  2. 随机生成输入数据。
  3. 把数据喂给暴力解法和正解。
  4. 比较输出,一旦不同就保存这组数据。

例如要测试一个数组算法,可以先生成:

cpp
        
1
2
3
4
int n = rnd(1, 8); for (int i = 1; i <= n; ++i) { cout << rnd(-10, 10) << " \n"[i == n]; }

小范围数据更容易暴露边界错误,也更方便手动分析。

代码实现

模板文件位置:/code/utils/random.cpp

cpp
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <bits/stdc++.h> using namespace std; mt19937 rng((unsigned)chrono::steady_clock::now().time_since_epoch().count()); // 生成 [l, r] 之间的随机整数。 int rnd(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } int main() { int n = rnd(4, 7); cout << n << "\n"; return 0; }

常见改法

固定随机种子

模板默认用当前时间作为种子,每次运行生成的数据通常不同。调试某一次错误时,可以把种子改成固定值:

cpp
        
1
mt19937 rng(114514);

这样每次运行都会生成同一批数据,方便复现。

生成 long long

如果需要生成更大的整数,可以把接口改成:

cpp
        
1
2
3
long long rndll(long long l, long long r) { return uniform_int_distribution<long long>(l, r)(rng); }

生成随机排列

随机排列常用于测试排序、图论编号、序列操作:

cpp
        
1
2
3
vector<int> p(n); iota(p.begin(), p.end(), 1); shuffle(p.begin(), p.end(), rng);

按概率生成事件

如果需要让某个事件以概率 p 发生,可以使用:

cpp
        
1
2
3
bool hit(double p) { return uniform_real_distribution<double>(0.0, 1.0)(rng) <= p; }

随机图模板里就使用了这个写法。

注意事项

  • rnd(l, r) 要求 l <= r,否则行为不符合预期。
  • 对拍时优先生成小数据,不要一开始就把数据范围拉满。
  • 固定种子适合复现错误;时间种子适合扩大随机覆盖面。
  • 随机测试不能证明程序一定正确,它只能帮你更快发现错误。
  • 如果题目边界很特殊,要手写边界数据补充测试,例如全相等、严格递增、全部为负数、最小规模等。

应用分类详解

随机数生成工具主要服务于“测试”,不是直接服务于某个算法模型。

一、对拍数据生成

典型模式: 有一个容易写但很慢的暴力解法,同时有一个复杂但高效的正解。

识别信号: 题目数据范围很大,但小范围下可以枚举;正解涉及贪心、数据结构、动态规划优化、复杂分类讨论。

核心建模: 把原题限制缩小,用随机数生成大量小样例,让暴力和正解相互校验。

二、边界压力测试

典型模式: 程序容易在极端输入上出错,例如数组长度为 1、全部元素相同、值域为负数、图没有边。

识别信号: 代码里有下标、区间、除法、取模、连通性判断、空集合判断。

核心建模: 用随机数生成普通数据,再手动混入极端数据,检查程序是否在所有分支上都能稳定运行。

三、构造随机操作序列

典型模式: 数据结构题需要支持多种操作,例如插入、删除、查询、区间修改。

识别信号: 题目输入由很多条操作组成,操作之间会改变后续状态。

核心建模: 随机选择操作类型,并维护一个合法状态,避免生成违反题意的操作。例如集合为空时不要生成删除操作。

参考

参考文档: