学习日常:造数据 - 上

学习日常:造数据 - 上

前言

上次自己造数据,感悟颇丰,今天就来写一下这个话题。

陈老师将这个任务交给我们时,给了我们一个板子,姑且叫它 build_data.cpp:

/*

测试数据生成说明:

1.本文件放入标程同文件夹

2.在标程内贴入右边语句(不要修改): freopen("data.in","r",stdin); freopen("data.out","w",stdout);

3.运行标程,生成标程的exe文件

4.修改下面 0-3 处地方,根据题意,打印 in 文件

5.运行本程序,批量生成对应的in文件和out文件

P.S.本程序无法覆盖同名文件,如果生成过程有错,需要手动删除之前同名文件

*/

#include

using namespace std;

char fn[50],op[50];

int rand(int l,int r) { //RAND_MAX*RAND_MAX

int tmp = (rand() * RAND_MAX + rand()) % (r - l + 1);

return l+tmp;

}

mt19937_64 ran(time(0)^rand()^(*(new int)));

//修改 0 :控制数据规模

long long b[11]= {10,100,1000,10000,100000,100000,100000,100000,100000,100000};

long long bb[11]= {10,10,10,10,100,500,1000,1000,1000,1000};

long long bbb[11]= {50,50,50,100000000,100000000,100000000,100000000,100000000,100000000,100000000};

long long bbbb[11]= {2,2,2,5,5,10,10,10,10,10};

int main() {

sprintf(fn,"A.exe"); //修改 1 :标程名称

srand(time(0));

for (int fd=0; fd<=19; fd++) { //修改 2:fd是 data文件的数字后缀 0-9

sprintf(op,"data.in",fn);

freopen(op,"w",stdout);

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 修改 3 : in文件的内容

// do something...

//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 修改 3 : in文件的内容

freopen("con","w",stdout);

system(fn);

sprintf(op,"ren data.in data%d.in",fd);

system(op);

sprintf(op,"ren data.out data%d.out",fd);

system(op);

}

return 0;

}

不知道大家觉得这个这个板子怎么样,反正我是觉得又丑又没用,我认为可以改进,于是就自己搓了一个 generator.cpp!

改进

发现缺陷

首先,我认为这个板子有以下的缺陷:

需要手动删除造错的数据,很不方便。

需要在标程内写以下语句:

freopen("data.in","r",stdin);

freopen("data.out","w",stdout);

感觉不太好,破坏了标程,其实就是强迫症。

将生成输入数据和批量生成数据的程序写到了一起,个人觉得看起来分开更好,层次更分明,也不需要对着这份 build_data.cpp 不停改,其实也是强迫症。

实现很丑,还有一些意义不明的神奇东西,其实还是强迫症。

于是针对这些问题,我自己搓了一个板子,将原来一个 build_data.cpp 拆成了两个程序:datamaker.cpp 和 generator.cpp。

一分为二

datamaker.cpp

datamaker.cpp 负责生成输入数据。

#include

#define to_int(x) atoi(x)

#define to_ll(x) atoll(x)

#define shuf(st,ed) shuffle(st,ed,default_random_engine(rnd()))

using namespace std;

typedef long long ll;

mt19937_64 rnd(time(0));

ll rand(ll l,ll r){return rnd()%(r-l+1)+l;}

int main(int argc,char *argv[]){

// do something...

return 0;

}

这里我写了一些造数据时常用的函数和宏,我们一个个来看:

shuf(st,ed):打乱 \([st,ed)\) 中的元素。

to_int 和 to_ll:将 C 风格字符串(字符数组)转化为其存储的整数类型。

rand(l,r):生成 \([l,r]\) 中的随机数。

\(p.s.\) 随机数的生成采用 mt19937_64。

其中,to_int 和 to_ll 的使用场景下文会讲到,实际操作时我们也只需要修改这个文件即可。

generator.cpp

generator.cpp 负责批量生成数据。

#include

#include

using namespace std;

typedef long long ll;

typedef unsigned long long ull;

typedef pair pii;

const int CAS=205;

int cas,st,cnt,flag;

string pn,dmn,stdn,u,order,fn;

// problem name,data maker name,std name,file name

struct Unknownn{ll a[CAS];}U;

vector v;

int main(){

// Part1.

cout<<"Loading",Sleep(500),system("cls");

cout<<"Problem's name? ";

cin>>pn;

cout<<"How many cases do you want? ";

cin>>cas;

cout<<"The beginning ID? ";

cin>>st;

cout<<"DataMaker's name?(No \".exe\") ";

cin>>dmn;

cout<<"Std's name?(No \".exe\") ";

cin>>stdn;

Sleep(500);

// Part2.

system("cls");

cout<<"How many unknown do you want? ";

cin>>cnt;

for(int i=1;i<=cnt;i++){

cout<<"Unknown "+to_string(i)+":\nname? ";

cin>>u;

cout<<"val?\n";

for(int j=1;j<=cas;j++) cout<<"Case #"<>U.a[j];

v.push_back(U);

cout<<"Unknown "+to_string(i)+" already configured!\n";

}

Sleep(500);

system("cls");

// Part3.

cout<<"What do you want to delete?\n1:All\n2:Only for use";

cin>>flag;

if(flag==1){

cout<<"Deleting..."<

system(("del "+pn+"*.in").c_str());

system(("del "+pn+"*.out").c_str());

Sleep(500);

}

cout<<"Making Input..."<

for(auto k:v){

for(int i=1;i<=cas;i++) cout<

puts("");

}

for(int i=st;i<=st+cas-1;i++){

order=dmn+".exe";

for(auto k:v) order+=" "+to_string(k.a[i-st+1]);

order+=" > "+pn+to_string(i)+".in";

system(order.c_str());

cout<

Sleep(1000);

}

Sleep(500);

cout<<"Making Output..."<

for(int i=st;i<=st+cas-1;i++){

system((stdn+".exe < "+pn+to_string(i)+".in > "+pn+to_string(i)+".out").c_str());

cout<<(stdn+".exe < "+pn+to_string(i)+".in > "+pn+to_string(i)+".out").c_str()<

}

cout<<"Succeed!";

return 0;

}

可能要一下子搞懂这么多东西有些困难,我们将其拆开进行讲解。

\(p.s.\) 实际上这份程序在造数据时也是无需修改的,只需写好标程和 datamaker.cpp,再直接运行这个程序即可,这个程序在使用过程中有很多引导性的提示词,直接运行它,根据提示词应该就可以使用,所以如果你只是来拿代码的,那你可以不用往下读了。

约定:在下文中会出现每个部分的输入输出,使用连续的下划线表示真正使用时需要输入内容的位置,后面使用括号表示举例和说明。

Part1. 基本配置

代码

// Part1.

cout<<"Loading",Sleep(500),system("cls");

cout<<"Problem's name? ";

cin>>pn;

cout<<"How many cases do you want? ";

cin>>cas;

cout<<"The beginning ID? ";

cin>>st;

cout<<"DataMaker's name?(No \".exe\") ";

cin>>dmn;

cout<<"Std's name?(No \".exe\") ";

cin>>stdn;

交互部分

开头会闪现一个 Loading,接着:

Problem's name? ______(问题的名称,会生成在数据的文件名中)

How many cases do you want? ______(测试点的数量)

The beginning ID? ______(测试点的起始编号,在分段造数据时用到)

DataMaker's name?(No ".exe") ______(datamaker.cpp应用程序的文件名,不需".exe")

Std's name?(No \".exe\") ______(标程应用程序的文件名,不需".exe")

解释

这部分代码阅读起来应该没有问题,很基础。

Part2. 设置参数

代码

struct Unknownn{ll a[CAS];}U;

vector v;

// Part2.

cout<<"How many unknown do you want? ";

cin>>cnt;

for(int i=1;i<=cnt;i++){

cout<<"Unknown "+to_string(i)+":\nname? ";

cin>>u;

cout<<"val?\n";

for(int j=1;j<=cas;j++) cout<<"Case #"<>U.a[j];

v.push_back(U);

cout<<"Unknown "+to_string(i)+" already configured!\n";

}

交互部分

How many unknown do you want? ______(需要调控的参数个数)

Unknown [k]:

name? ______(参数的名称)

解释

用于调控数据规模、增加数据限制,后文再讲。

Part3. 生成数据

代码

// Part3.

cout<<"What do you want to delete?\n1:All\n2:Only for use";

cin>>flag;

if(flag==1){

cout<<"Deleting..."<

system(("del "+pn+"*.in").c_str());

system(("del "+pn+"*.out").c_str());

Sleep(500);

}

cout<<"Making Input..."<

for(auto k:v){

for(int i=1;i<=cas;i++) cout<

puts("");

}

for(int i=st;i<=st+cas-1;i++){

order=dmn+".exe";

for(auto k:v) order+=" "+to_string(k.a[i-st+1]);

order+=" > "+pn+to_string(i)+".in";

system(order.c_str());

cout<

Sleep(1000);

}

Sleep(500);

cout<<"Making Output..."<

for(int i=st;i<=st+cas-1;i++){

system((stdn+".exe < "+pn+to_string(i)+".in > "+pn+to_string(i)+".out").c_str());

cout<<(stdn+".exe < "+pn+to_string(i)+".in > "+pn+to_string(i)+".out").c_str()<

}

cout<<"Succeed!";

交互部分

What do you want to delete?

1:All(删除所有对应问题名下的之前生成的数据)

2:Only for use(不物理删除文件,直接创建新文件或者覆盖之前的同名文件)

______(输入1或2,表示删除方式)

......(删除指令)

Making Input...

......(生成输入文件指令)

Making Output...

......(生成输出文件指令)

Succeed!

解释

这里运用了 cmd 中运行应用程序并指定输入输出位置的技术。

// cmd

test.exe [参数,空格分隔] < [输入文件名] > [输出文件名]

合二为一

理论部分

为什么前面的小标题叫 “一分为二”,这里又叫 “合二为一” 呢?因为我们还需要一种媒介,使得 generator.cpp 可以与 datamaker.cpp “沟通”,使它们在结构上分开,在功能上合为一个整体。

由于 generator.cpp 是带交互性的,所以我们需要让 generator.cpp 能将交互内容 “告诉” datamaker.cpp ,“指导” datamaker.cpp 的工作。

具体而言,datamaker.cpp 生成的数据应有与数据点相关的限制,如不同的数据规模、特殊性质等,那我们的 generator.cpp 就做到了这一点。

我们看回 datamaker.cpp:

#include

#define to_int(x) atoi(x)

#define to_ll(x) atoll(x)

#define shuf(st,ed) shuffle(st,ed,default_random_engine(rnd()))

using namespace std;

typedef long long ll;

mt19937_64 rnd(time(0));

ll rand(ll l,ll r){return rnd()%(r-l+1)+l;}

int main(int argc,char *argv[]){ // main 函数的参数是干什么用的?

// do something...

return 0;

}

你知道 main 函数的参数是干什么用的吗?

它就是两个程序 “沟通” 的媒介!

再看回 generator.cpp 的 Part 2:

struct Unknownn{ll a[CAS];}U;

vector v;

// Part2.

cout<<"How many unknown do you want? ";

cin>>cnt;

for(int i=1;i<=cnt;i++){

cout<<"Unknown "+to_string(i)+":\nname? ";

cin>>u;

cout<<"val?\n";

for(int j=1;j<=cas;j++) cout<<"Case #"<>U.a[j];

v.push_back(U);

cout<<"Unknown "+to_string(i)+" already configured!\n";

}

刚开始我给这个部分取了一个小标题:设置参数。

它的作用就是帮助你设置一些限制 datamaker.cpp 的参数,具体的调用参数则是在 Part 3 完成的:

// cmd

test.exe [参数,空格分隔] < [输入文件名] > [输出文件名]

// 这里↑

接着只需在 datamaker.cpp 中获取这些参数即可。

其中,main 函数的参数就是接受这些参数的部分。

int argc:接受到的参数个数。

char *argv[]:每个参数的 C 风格字符串(字符数组)。

因为我太菜了,还没写出不定长的参数,所以其实第一个参数没啥用。

第二个参数则是以 C 风格字符串(字符数组)的形式给出的,从 \(1\) 开始编号每个参数,我们可以使用 to_int 和 to_ll 将其转化为整数类型。

目前的参数是用 long long 类型,仅支持整数类型,大家感兴趣的话可以加上别的类型,还可以写成模板的形式;或者可以写成按照数据点顺序排布的参数配置,这样每个数据点的参数数量可以都不一样。

实例部分

我们以 P1003 铺地毯 为例。

看它对数据范围的表述:

【数据范围】

对于 \(30\%\) 的数据,有 \(n \le 2\)。

对于 \(50\%\) 的数据,\(0 \le a, b, g, k \le 100\)。

对于 \(100\%\) 的数据,有 \(0 \le n \le 10^4\), \(0 \le a, b, g, k \le {10}^5\)。

我们不要那么复杂,不管 \(a, b, g, k\),简化一下:

【数据范围】

对于 \(30\%\) 的数据,有 \(n \le 2\)。

对于 \(100\%\) 的数据,有 \(0 \le n \le 10^4\)

这里有对 \(n\) 的限制,而是有数据点决定的,所以我们以数据点编号为参数,写出 datamaker.cpp:

#include

#define to_int(x) atoi(x)

#define to_ll(x) atoll(x)

#define shuf(st,ed) shuffle(st,ed,default_random_engine(rnd()))

using namespace std;

typedef long long ll;

mt19937_64 rnd(time(0));

ll rand(ll l,ll r){return rnd()%(r-l+1)+l;}

int t,n;

int main(int argc,char *argv[]){

t=to_int(argv[1]);

n=(t<=3?rand(1,2):rand(200,1000)); // 200 是随便设的下限

return 0;

}

这样就写好了,具体在 generator.cpp 中就创建一个参数,参数值直接赋为数据点编号即可,交互部分参照上文。

当然,这只是一个比较粗略的实现,你当然可以通过别的参数设置或判断方式来更加精细的配置。直接以 \(n\) 的范围为参数也可以,你甚至可以限制一个 \(n\),再让它波动一下,比如 \(n=[n-\sqrt{n},\min(n+\sqrt{n},20)]\)

尾声

那希望这篇文章对大家有帮助,你也可以继续改进我的板子,还可以私信跟我交流。

至于这篇文章的后继:科技·工程:造数据 - 下,肯定不会咕着的,已经在写了哦!

\(\bf{完结撒}\color{pink}{\bf{花}}\color{black}{\bf{!}}\)

相关推荐

幻象:吸血
365bet繁体中文

幻象:吸血

📅 08-29 👁️ 9804
金螳螂002081加入自选股
office365 登录

金螳螂002081加入自选股

📅 07-07 👁️ 4807
长虹手机报价
365足彩推荐

长虹手机报价

📅 09-26 👁️ 8003