所有系统下对拍技巧详解,看一篇就够了!

对拍技巧详解

更新日志

update on 20240803 修正了 Windows 系统下 check.bat 中驴头不对马嘴的文件名

前言

这篇文章编写不易,求你先点个赞,谢谢!

对于各位 OIers 来说,在考场上遇到一道很复杂的大模拟的题的时候,你肯定是会先写出一个能得到部分分暴力算法,再在其基础上进行算法优化,试图取得更高的分数

但是,你有时候不敢肯定你优化后的代码就一定是对的。

所以,到底应该怎么检验自己优化后代码的正确性呢?

这篇文章将带来一个很有用的方法:对拍

请注意,由于这篇文章中的内容大都环环相扣,所以不建议阅读时跳过任何部分

原理介绍

我们这个对拍分为以下几步:

  1. 利用数据生成器(gen.cpp)随机生成一组不是很大的数据(data.txt)(不然你的暴力会死活都跑不出来)
  2. 让你一开始写的那个暴力程序(baoLi.cpp)在读入这组数据后跑出一个结果(result1.txt)
  3. 让你优化后吃不准对不对的程序(youHua.cpp)也用这组数据跑出一个结果(result2.txt)
  4. 利用电脑自带的比较文件命令
    去比较你的这两个结果,如果一样,说明正确,输出 AC,否则说明有误,输出WA,并终止对拍程序

非常妙吧!

实现

我是在 Mac 下进行演示的,所以

对于 Windows 用户,如果在两个操作系统下有什么不同的话,我会特殊说明的。

PS: 为了照顾到各种电脑,本教程中一律使用 C++ 中最为简朴的 freopen 来处理文件的读写,而不使用一些容易使某些电脑认不得的 <> 的文件操作符号(这句话看不懂也不影响任何东西)

首先,对于使用 Mac 或 Linux 的读者你需要这么建立一个文件夹:

建立文件夹

而对于使用 Windows 的读者,请将以上图片中的 check.cpp 改成 check.bat 来建文件夹。

然后,我们一步一步来。

我们以计算 a + b 为例,哈哈哈。

1. 数据生成器(gen.cpp)

先上对于 a + b 这题的数据生成器代码,你直接去看里面的注释就可以了

#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    freopen("data.txt","w",stdout);
    // 这是为了将生成出来的数据放入 data.txt 中,方便后续读取使用
    srand(time(0));
    // 设置随机种子,注意:这里必须不能是定值,不然无论如何随机出来的数据都是一样的了!
    // 建议使用 time(0) 即函数返回的当前时间来作为种子
    int a = rand() % 50000 + 1;
    // 随机一个 a 出来
    /*
    rand() 函数会利用 C++ 中的一个伪随机数生成算法返回一个“随机数”
    加 1 的目的:
    (rand() % 50000) 这个表达式的值的范围只有 0 ~ 49999(因为有取模)
    而我们如果想让 a 是从 1 ~ 50000 之间随机取出的数,就应当加 1,好理解吧
    */
    int b = rand() % 50000 + 1;
    // 生成 b 的原理与 a 相同
    cout << a << " " << b << "\n";
    return 0;
}

那么除了 a + b 这道简单的题外,我有一些数据生成的技巧:

1. 生成 1 ~ X 之间的正整数

那么应当是

int a = rand() % x + 1;

这个前文有提到过为什么,这里就不再赘述

2. 生成[L,R]范围内的整数

这个相比于 1 应该更加通用,因为 1 相当于是它的一种特殊情况。

int a = rand() % (r - l + 1) + l;

那在这里 r - l + 1 就是一个偏差值,相当于是表示 a 比 l 大了多少,

rand() % (r - l + 1) 的范围在 0 ~ (r - l) 之间,

而加上 l 之后范围偏到了 l ~ r 之间,符合要求!

3. 生成特定条件的数

这里为了让你有较高的可自定义性,给出一些伪代码,提供一个思路

定义 x; // 类型可以自定义
x = [获取随机数的方法]; // 方法可以自定义
while(!(x 应符合的条件)) // 只要不满足条件就继续,(条件也可以自定义)
	x = [获取随机数的方法]; // 再次进行随机

但是,如果用这个思路,你需要注意一点:如果你的条件特别苛刻,而你的随机范围又特别大,这容易导致你的这段代码特别慢,从而影响对拍效率。所以在数据生成方面也应当仔细考虑。

4. 一些思想上的建议

想想看,对拍是为了什么,它是为了让暴力程序和优化后的程序跑同一组数据后比对结果的,但是如果你的数据太大了,容易导致你的暴力程序都跑不出结果(哈哈哈),所以在定生成的数据的范围的时候,请注意不要定过大。

还有一点,对于复杂的大模拟题,随机数据其实很弱,是不一定能 hack 掉你的代码的,所以在对拍正确多组数据后,请务必不要掉以轻心,请仔细思考还有什么小点没考虑到,并依据这种特殊性质去修改 gen.cpp 来制造 hack 数据。

2. 暴力程序(baoLi.cpp)

还是对于 a + b 这题来看,暴力程序长这样:(很好懂吧

#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    freopen("data.txt","r",stdin); // 从 gen.cpp 生成的数据中读入
    freopen("result1.txt","w",stdout); // 将运行结果写入 result1.txt 中
    int a,b;
    cin >> a >> b;
    // 一下为特别暴力的做法
    int ans = 0;
    for(int i = 1; i <= a; i++)
        ans++;
    for(int j = 1; j <= b; j++)
        ans++;
    cout << ans << "\n";
    return 0;
}

如果你看了晕,我来总结一下,说白了就是在你编写好的暴力程序的 main() 函数里的最前面加上这两行代码即可:

freopen("data.txt","r",stdin); // 从 gen.cpp 生成的数据中读入
freopen("result1.txt","w",stdout); // 将运行结果写入 result1.txt 中

3. 优化后的程序

还是对于 a + b 这道题,优化后的程序应该如此:

#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    freopen("data.txt","r",stdin); // 从 gen.cpp 生成的数据中读入
    freopen("result2.txt","w",stdout); // 将运行结果写入 result2.txt 中
    int a,b;
    cin >> a >> b;
    cout << a + b << "\n";
    return 0;
}

总结一下,就是在 main() 函数里的最前面加上以下两句:

freopen("data.txt","r",stdin); // 从 gen.cpp 生成的数据中读入
freopen("result2.txt","w",stdout); // 将运行结果写入 result2.txt 中

4. 检验程序

注意了,前三个程序无论在 Mac 下还是 Windows 下都是通用的,但到了这里,这个检验程序需要调用系统指令,所以开始有了一些区别。

Mac 或 Linux 下的 cpp 检验程序(这两个操作系统都通用):(仔细看代码里的注释,解释都在里面)

#include <bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    system("clear");                        // 为了整洁,先清空屏幕
    cout << "对拍检验程序启动\n\n";            // 提示语句
    cout << "开始编译数据生成器\n";            // 提示语句
    system("g++ gen.cpp -o gen");           // 重新编译 gen.cpp
    cout << "编译成功\n\n";                  // 提示语句
    cout << "开始编译暴力程序\n";             // 提示语句
    system("g++ baoLi.cpp -o baoLi");      // 重新编译 baoLi.cpp
    cout << "编译成功\n\n";                 // 提示语句
    cout << "开始编译优化程序\n";            // 提示语句
    system("g++ youHua.cpp -o youHua");   // 重新编译 youHua.cpp
    cout << "编译成功\n\n";                // 提示语句
    cout << "开始对拍测试\n\n";             // 提示语句
    int testCase = 0;                     // 定义测试点编号
    while(true)
    {
        testCase++;                       // 每次测试点编号都加一
        system("./gen");                  // 生成数据
        system("./baoLi");                // 运行暴力代码
        int TTT = clock();                // 记录要测试的代码运行前的时间
        system("./youHua");               // 运行要测试的优化后的代码
        cout << "Test case #" << testCase << " Time: " << (double)(clock() - TTT) / CLOCKS_PER_SEC << "s\n";
        // 将运行 youHua 后的时间减去运行 youHua 前的时间,得到这份代码跑了多久,并输出
        if(system("diff result1.txt result2.txt") == 0)
        // 这个 diff 是 Mac 以及 Linux 下都有的用于非常简陋地比较两个文件是否一样的指令
        // diff 指令返回 0 表示一样,返回 1 表示有差异
            cout << "Accepted on test case #" << testCase << "\n\n";
        else
        {
            cout << "Wrong answer on test case #" << testCase << "\n\n";
            break;
            // 为了保存下来这组跑出错误的数据以便后续 debug,我们迅速终止对拍并留住这组数据
        }
    }
    cout << "对拍结束!\n"; // 提示语句
    system("rm gen baoLi youHua check");
    cout << "已删除临时文件!\n\n"; // 提示语句
    // 将程序编译生成的临时可执行文件删除,如果想保留可执行文件的话可以将这两行代码注释掉
    return 0;
}

而 Windows 下有极大的不同!!!

由于如 diff./g++ ... -o ... 等指令在 Windows 系统中没有或不一定有,我们得放弃用 C++ 这门语言来写 check 了

我们要用一个批处理文件,它的名字应为 check.bat,注意后缀是 .bat

代码长这样:

@echo off
:start
gen.exe
baoLi.exe
youHua.exe
fc result1.txt result2.txt
if not errorlevel 1 goto start
pause
go start

很明显,这个脚本会比前面 Mac 下的那个 cpp 简陋很多,但我也没什么办法

由于 bat 代码中写注释太繁琐了,咱直接一行一行讲吧:

其他东西都不用管,我就讲一下这里的核心吧

(由于我现在手头是 Mac 电脑,而这个 bat 是我很久以前写的,所以我也无法运行它看结果,只能凭之前的记忆讲一下)

第三行 gen.exe 意思就是运行数据生成器编译后产生的 .exe文件
第四行、第五行同理

所以在 Windows 下,在运行 check.bat 前,你得先用 Dev-C++ 或者任意其他 IDE 编译 gen.cppbaoLi.cppyouHua.cpp 这三个代码,

并且获得它们对应的可执行文件( .exe文件)

再看第六行 fc 正是 Windows 下自带的比较文件的指令,可以理解成与 Mac 下的 diff 差不多,也是两个文件一样就返回 0,不一样返回 1

对于第七行,它的用途是对于前一行 fc 返回回来的值,如果不是 1 说明正确,则继续对拍,不然停下,并等待(pause)。

5.最后一步

大功告成! \huge{大功告成!} 大功告成!

你仅需编译运行 check.cpp 或运行 check.bat 即可。

Mac 运行结果如下:

你的 youHua.cpp 如果写对了,检验程序运行后会是这样(也就是每组数据都AC):

AC

如果写的有问题并且被检验出来了(即有一组数据 WA了),那么会是这样:

WA

小结

对拍是一个非常实用的赛场技巧,它可能看起来步骤非常多,但是你精通对拍的概念和本质了之后,根据对拍本身的原理就可以轻松地对拍了。

这篇文章耗时将近一周(我每天抽空写一点),请务必轻按鼠标点一个赞,谢谢!

如果发现这篇文章有任何不清楚或不正确的地方,欢迎各位读者私信我或评论提供建议,我将在看见后进行更改

  • 33
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值