算法竞赛进阶指南读书笔记——0x72随机数据生成与对拍

随机数据生成与对拍

适用场景:

  1. OI赛制比赛验证“高分解法”
  2. OJ做题无法下载数据或下载数据规模过大
  3. 出题

随机数据生成

基础知识:

头文件cstdlib:

  • RAND_MAX:常量,Windows下一般为32767,Unix下一般为2147483647

  • rand():返回 0 ∼ R A N D _ M A X 0\sim RAND\_MAX 0RAND_MAX之间的随机整数

  • srand(unsigned seed):将seed用作随机种子,seed确定后随机序列便确定;不执行srand时seed默认为1

头文件ctime:

  • time(0):返回Unix纪元(1970/1/1)到现在的秒数

随机数生成模板:

#include <cstdlib>
#include <ctime>
int random(int n) { return (long long)rand() * rand() % n; } // [0, n-1]
int main() {
  srand((unsigned)time(0));
  // ...
}

注:

生成随机实数:较大随机整数除以10的次幂

生成正数与负数:生成 [ 0 , 2 n ] [0,2n] [0,2n]间的随机整数,再减n

例1 随机生成整数序列:随机生成 n ⩽ 1 0 5 n\leqslant 10^5 n105个绝对值小于等于 1 0 9 10^9 109的整数。

int n = random(1e5) + 1; // [1, 10^5]
int m = 1e9;
for (int i = 1; i <= n; i++)
  a[i] = random(2 * m + 1) - m; // [-10^9, 10^9]

例2 随机生成区间列:随机生成 m m m [ 1 , n ] [1,n] [1,n]的子区间(用于数据结构题)

for (int i = 1; i <= m; i++) {
  int l = random(n) + 1, r = random(n) + 1; // [1, n]
  if (l > r) swap(l, r);
  printf("%d %d\n", l, r);
}

例3 随机生成树:随机生成一棵 n n n个点的树,用 n n n n − 1 n-1 n1边无向图形式输出,每条边附带 1 0 9 10^9 109内的正整数权值

for (int i = 2; i <= n; i++) {
  int fa = random(i - 1) + 1; // ∀i∈[2, n]向fa∈[1, i-1]随机连一条边i<-fa
  int val = random(1e9) + 1;
  printf("%d %d %d\n", fa, i, val);
}

例4 随机生成图:随机生成一张 n n n m m m边的无向图,保证不存在重边、自环,且连通( 5 ⩽ n ⩽ m ⩽ n ( n − 1 ) 4 ⩽ 1 0 6 5\leqslant n\leqslant m\leqslant \dfrac{n(n-1)}{4}\leqslant10^6 5nm4n(n1)106)

const int N = 1e6 + 5;
pair<int, int> e[N]; // 存边
map<pair<int, int>, bool> h; // 去重
for (int i = 1; i < n; i++) { // 生成树,保证连通
  int fa = random(i) + 1;
  e[i] = make_pair(fa, i + 1);
  h[e[i]] = h[make_pair(i + 1, fa)] = 1; // 正反向置1
}
for (int i = n; i <= m; i++) { // 生成剩余边
  int x, y;
  do {
    x = random(n) + 1, y = random(n) + 1;
  } while (x == y || h[make_pair(x, y)]); // 自环,重边
  e[i] = make_pair(x, y);
  h[e[i]] = h[make_pair(y, x)] = 1; // 正反向置1
}
random_shuffle(e + 1, e + m + 1); // 随机打乱
for (int i = 1; i <= m; i++)
  printf("%d %d\n", e[i].first, e[i].second);

注:

  1. n n n过小时直接特判:此时完全图中边数与树的相差不大
  2. m m m过大时效率过低:生成补图(完全图-原图)
  3. 树、图结构三类极端数据:
    • 链形数据: N N N节点 N − 1 N-1 N1条边的长链——造成很大的递归深度:卡点分治
    • 菊花形数据: 1 1 1号为中心, 2 ∼ N 2\sim N 2N与之相连使得 1 1 1号点度为 N − 1 N-1 N1——卡缩点
    • 蒲公英形数据:链+菊花

对拍

准备工作:sol.cpp,bf.cpp,random.cpp

sol.cpp->sol.exe(test)

input
output
data.in
sol.exe
data.out

bf.cpp->bf.exe(std)

input
output
data.in
bf.exe
data.ans

random.cpp->random.exe(generator)

output
random.exe
data.in

Windows系统对拍程序(cpp):

for (int T = 1; T <= MAX_PADDING_ROUND; T++) {
  system("C:\\random.exe");
  double st = clock(); // Windows(ms),Unix(s)
  system("C:\\sol.exe");
  double ed = clock();
  system("C:\\bf.exe");
  if (system("fc C:\\data.out C:\\data.ans")) { // 一旦WA立即停止,此时data.in为引发错误的数据
    puts("Wrong Answer");
    return 0;
  }
  printf("Accepted, Test #%d, Time used %.0lfms\n", T, ed - st);
}

Windows系统对拍程序(python):

import random
import os
import time
import filecmp

MAX_PADDING_ROUND = 1000


def compile():
    fp = os.path.dirname(__file__) # 获取当前路径
    os.system(f"cd {fp} && g++ std.cpp -o std.exe") # &&连接两条指令
    os.system(f"cd {fp} && g++ test.cpp -o test.exe")


def generate(n): # 将random.cpp的逻辑放在这里(n为数据规模)
    fp = os.path.dirname(__file__)
    with open(f"{fp}/in", "w") as f:
       # print(...,file=f)


if __name__ == "__main__":
    fp = os.path.dirname(__file__)
    compile() # 动态编译
    for i in range(MAX_PADDING_ROUND):
        print(f"TEST #{i}:")
        generate(i) # 可设定数据规模随对拍轮数变化
        os.system(f"cd {fp} && std.exe <in >ans") # 输入输出重定向,原程序可直接与stdin,stdout交互
        print("ans prepared") # 防止暴力或std卡死
        st = time.time() # 单位s
        os.system(f"cd {fp} && test.exe <in >out")
        ed = time.time()
        if not filecmp.cmp(f"{fp}/ans", f"{fp}/out"): # 文件内容比对
            print(f"WA!")
            break
        print(f"Time:{ed-st}sec")
        
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值