1009 字
5 分钟
在Windows下编写C++对拍程序(真随机数)

对拍,即将相同的数据输入两个不同程序,比对其输出结果。可以用于在ACM/OI等比赛中用暴力算法检测标算的正确性,寻找使程序出错的样例数据。

随机数生成器#

要编写好用的对拍程序,首要的是要编写一个随机数生成器,便于产生多样化的数据。

朴素随机数生成器#

相信很多人都见过下面这种写法:

//DataGenerator_naive.cpp
#include <iostream>
#include <cstdio>
#include <ctime>
using namespace std;

int main(int argc, char *argv[])
{
    int seed = time(NULL);
    srand(seed);

    //以下代码可以生成10个随机整数
    int n = 10;
    for (int i = 1; i <= n; i++)
    {
		printf("%d ", rand());
    }

    return 0;
}

但是这样的写法有个问题,在Windows下time(NULL)获取的是自 197019701111 日至今经过的秒数,也就是说同一秒内获得的随机数种子是一样的,即我们每秒只能获得一组有效的随机样例,这样的效率太低,不是我们可以接受的。

利用命令行随机数种子#

事实上,Windows命令行自带了一个随机数生成器%random%,每次调用都可以获得一个不同的整数,能够作为随机数种子,可以将上面的程序改写一下:

//DataGenerator_cmd.cpp
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>
#include <algorithm>
using namespace std;
stringstream ss;

int main(int argc, char *argv[])
{
    int seed = time(NULL);
    if (argc > 1) //如果有参数
    {
        ss.clear();
        ss << argv[1];
        ss >> seed; //把参数转换成整数赋值给seed
    }
    srand(seed);

    //以下代码可以生成10个随机整数
    int n = 10;
    for (int i = 1; i <= n; i++)
    {
		printf("%d ", rand());
    }

    return 0;
}

这里我们利用了main函数的传入参数,argc表示参数个数,argv[]表示每个参数的值,以字符串形势表示。

以下命令可以通过调用%random%,并传入DataGenerator_cmd.exe,使得每次运行都能获得不同的种子,并将生成的随机数写入in.txt,以供目标程序读取数据:

DataGenerator_cmd.exe %random% > in.txt

利用系统的加密秘钥生成器生成随机数#

下面这个 WinRandom 类调用了系统的加密秘钥生成器,这个生成器调用内核生成秘钥,所以是硬件的真随机数,可将其写成头文件:

//WinRandom.h
#include <windows.h>
#include <wincrypt.h>
#include <assert.h>
class WinRandom
{
    HCRYPTPROV handle;

public:
    WinRandom()
    {
        handle = NULL;
        CryptAcquireContext(
            (HCRYPTPROV *)&handle, NULL, NULL,
            PROV_RSA_FULL, 0);
    }
    ~WinRandom()
    {
        if (handle != NULL)
            CryptReleaseContext(handle, 0);
    }
    bool randBuf(void *dest, int len)
    {
        if (handle == NULL)
            return false;
        return CryptGenRandom(handle, len, (BYTE *)dest);
    }
#define _(func, typ)                                \
    typ func()                                      \
    {                                               \
        typ ret = 0;                                \
        assert(randBuf((void *)&ret, sizeof(ret))); \
        return ret;                                 \
    }
    _(randInt, int)
    _(randLong, long long)
    _(randUnsigned, unsigned)
    _(randUnsignedLong, unsigned long long)
    _(randShort, short)
    _(randUnsignedShort, unsigned short)
    _(randChar, char)
    _(randUnsignedChar, unsigned char)
    _(randSignedChar, signed char)
};

将上面的程序再改写以下,调用WinRandom类,就可以实现真随机数:

//DataGenerator_WR.cpp
#include <iostream>
#include <cstdio>
#include "WinRandom.h"
using namespace std;

int main()
{
    WinRandom R;
    //用 R.randInt() 来返回一个随机的int型变量

    //以下代码可以生成10个随机整数
    int n = 10;
    for (int i = 1; i <= n; i++)
    {
		printf("%d ", R.randInt());
    }

    return 0;
}

对拍程序#

这里的对拍程序通过 system()函数在C++程序内执行命令行指令,你可以自定义目标程序位置和随机数生成器位置等:

//checker.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <windows.h>
using namespace std;

int main()
{
    int type;	//选择生成器类型
    printf("input random generator type:\n");
    printf("1: naive\n");
    printf("2: cmdRandom\n");
    printf("3: WinRandom\n");
    scanf("%d", &type);

    int t = 100000;
    string Std = R"("{标算位置}.exe")";
    string Mtd = R"("{你的程序位置}.exe")";
    string DataGenerator;
    if (type == 3)
        DataGenerator = R"("{WR生成器位置}\DataGenerator_WR.exe")";
    else
        DataGenerator = R"("{cmd生成器位置}\DataGenerator_cmd.exe")";
    string in_txt = "in.txt";
    string mtd_out_txt = "mtd-out.txt";
    string std_out_txt = "std-out.txt";
    while (t--)
    {
        if (type == 2)
            system((DataGenerator + " %random%" + " > " + in_txt).data());
        else
            system((DataGenerator + " > " + in_txt).data());
        // printf("%s\n", (in_txt + " < " + Mtd + " > " + mtd_out_txt).data());
        system((Mtd + " < " + in_txt + " > " + mtd_out_txt).data());
        system((Std + " < " + in_txt + " > " + std_out_txt).data());
        if (system(("fc " + mtd_out_txt + " " + std_out_txt).data()))
            system("pause");
    }
    return 0;
}

请参阅#

在Windows下编写C++对拍程序(真随机数)
https://blog.vonbrank.com/posts/std-checker/
作者
Von Brank
发布于
2021-03-28
许可协议
CC BY-NC-SA 4.0