由来:笔者在编一道叫做 三国杀 的题时,因为作者不会造数据,所以他在网上断断续续翻找了整整2周,最后弄懂了如何用C++造数据的些许皮毛。笔者将在本 B l o g Blog Blog 给出用C++造数据的,较为详细的讲解。
前言:本篇 B l o g Blog Blog 引用了以下文章:
C++中rand()函数的用法 f r o m from from csdn Kallou
C++中 rand() 通用公式及总结
f
r
o
m
from
from csdn 沾liana
The First Step
在所有之前,你需要知道几个标准库函数:
f r e o p e n : freopen: freopen:对于 OIer 来说应该很熟悉了,参考百度百科,其定义是:
freopen
是被包含于C
标准库头文件<stdio.h>
中的一个函数,用于重定向输入输出流。该函数可以在不改变代码原貌的情况下改变输入输出环境。以指定模式重新指定到另一个文件,模式用于指定新文件的访问方式。
当然,这么写也不是一般人能看得懂的。举个例子,平时我们在参加信竞(比赛)时,总会加上两行代码:
freopen("1.in","r",stdin);
freopen("1.out","w",stdout)
要想弄懂这两行代码是什么意思,就应先搞清楚它的格式(声明):
FILE *freopen(const char * restrict filename, const char * restrict mode, FILE * restrict stream);
其中, filename
表示需要重定向到的文件名或文件路径;
而 mode
代表文件访问权限的字符串。例如,
r
r
r 表示“只读访问”、
w
w
w 表示“只写访问”。顾名思义,“只读访问”即只是读取该文件中的数据,配合上文的 filename
和下文的 stream
的 stdin
,起到从 filename
指定的文件中读取数据作为程序的输入的作用;而“只写访问”亦是如此。其配合下文的 sstream
的 stdout
,起到将程序的输出存储到 filename
所指定的文件中。
stream
表示需要被重定向的文件流。依上文,我们主要使用 stdin
及 stdout
。
现在,让我们看回最上面的两行代码的例子。通过上面的解释,我们可以得到第一行表示从一个叫 “1.in” 的文件中读取数据作为程序输入,第二行表示在程序运行完后,将程序的运行结果存到一个叫 “1.out” 的文件中。
s p r i n t f ( ) : sprintf(): sprintf(): 还是让我们引用百度百科:
sprintf
指的是字符串格式化命令,主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string
所指向的字符串。
接下来,上格式(声明):
int sprintf(char *string, char *format [,argument,...]);
显然,函数内包含三个参数。还是举个例子:
//把整数123打印成一个字符串保存在s中。
string s;
sprintf(s, "%d", 123); //产生"123"
依注释,上面代码相当于将字符串s保存为123。现在看来,它很简单。但我们造数据一般要一次性造数个,那么我们就要把它放到 for()
循环里边去。
string s;
for(int ii = 1; ii <= 10; ii ++){
sprintf(s, "%d.in", ii);
cout << s << endl;
}
上面的代码即在每次循环时,将s保存为 ii.in
的格式,而ii
作为循环变量,在每次循环++。
某些读者可能觉得还是不容易理解,让我们看看程序的输出:
1.in
2.in
3.in
4.in
5.in
6.in
7.in
8.in
9.in
10.in
这样就一目了然了。
r a n d ( ) : rand(): rand(): 一个随机数产生器,它会返回一个从 0 0 0 到最大随机数的任意整数。通常,这个最大随机数为RAND_MAX,且其至少为 32767 ( i n t ) 32767(int) 32767(int) 。
但是,值得注意的是,rand()函数不接受参数,默认以 1 1 1 为种子(即起始值)。这样就导致了随机数生成器总是以相同的种子开始,所以形成的(伪)随机数列也相同,也就失去了它的随机意义。
为了令 rand()
函数的返回值随机,我们还需要人为定义随机数种子。这就扯到了本函数的一相关函数 srand()
。它可以指定不同的数为种子。如果是用户直接输入种子,种子相同,伪随机数列也相同。随机生成可以利用 time(0)
的返回值或 NULL
来当做种子。如:
srand(time(0)) //记得写time.h头文件
srand(NULL)
这就可以产生随机数了:
srand(time(0));
int a=rand();
此时,我们便得到了一个可能范围为0~RAND_MAX的随机数 a a a 。
以下列举获取部分随机数的方法:
要取得 [a,b) 的随机整数,使用(rand() % (b-a))+ a
(结果值 [a,b) )。
要取得 [a,b] 的随机整数,使用 (rand() % (b-a+1))+ a
(结果值 [a,b] )。
要取得 (a,b] 的随机整数,使用 (rand() % (b-a))+ a + 1
(结果值(a,b] )。
The Second Step
经过上文的阅读,相信读者已经明白两个函数的基本用法。现在,让我们以A+B为例,了解如何批量造数据。
#include<bits/stdc++.h>
using namespace std ;
int main(){
char c[10001] ;
srand(time(0)) ;
for( int i = 1 ; i <= 10 ; i++ ){
sprintf(c , "%d.in", i) ;
freopen(c , "w" , stdout) ;
int a = rand() , b = rand() ;
printf("%d %d", a , b) ;
sprintf(c , "%d.out", i) ;
freopen(c , "w" , stdout) ;
printf("%d", a+b) ;
}
return 0 ;
}
对于上面的代码,它的原代码是这样的:
#include<bits/stdc++.h>
using namespace std ;
int main(){
cin>>a>>b;
cout<<a+b;
return 0 ;
}
让我们先一行一行了解代码的含义与作用。
从主函数开始,第一行,我们定义了一个字符数组,该数组用以存储所造出的数据储存在的文件的名称。
接着,便出现了上文提到的 srand(time(0))
,要想使用造随机数的 rand()
函数,就必须先人为定义种子,其实在造数据中,我们可以都以时间为种子,所以到时候读者自己写的时候,可以照抄。
再下一行,便出现了一个额外的 for
循环,这个循环用于不断地造出输入输出数据,每一次循环便可造一组数据。其中,循环变量的取值由你自己决定,上述例子即从数据
1
1
1 造到数据
10
10
10 。
循环内部,先是一句 sprintf(c , "%d.in", i)
与 freopen(c , "w" , stdout)
,表示不断地将
c
c
c 数组赋值为“1.in”,“2.in” 以此类推的形式,接着再规定接下来的输出均存入以当前
c
c
c 数组命名的文件中。(当然,取别的名字也行,只不过这样更简洁明了)
接着,便是变成了 int a = rand() , b = rand()
的 cin>>a>>b
,这里就是把原本题目要输入的(也就是造数据要造出来的)变成随机数。
由于先造的是输入文件,所以我们要先把题目数据输入中给出的 a a a 与 b b b 输出。
再下两行,与代码 7 7 7 、 8 8 8 行的作用相似,只不过这时存的是输出。
又因为此时要造输出文件,所以应输出题目本身要求输出的 a + b a+b a+b 。
现在,我们已经了解了上述造数据代码的作用,让我们运行代码,看看效果。(注意:通常情况下,代码应当是在运行后未经任何认为操作就显示结束。)
先将代码放入一个文件夹(这样可以方便到时候压缩,如果放在桌面很容易与桌面其它文件混乱)
运行:
我们便得到了 10 10 10 个完整的数据(这里随机打开一组以验证数据的准确性。):
The Third Step
经过了上面的解析,相信读者已经对造数据有了初步的了解。现在,我们只需把他们压缩,再上传后即可,这里不再演示。(不同OJ可能有不同要求)
小技巧: rand()
函数后通常可加 “%...
” 以规定所生成的数的范围。如 rand()%100
表示所生成的数的范围为0~99。
最后要注意的是,某些题在造数据的时候,应当每造一个就初始化一次,至于初始化的值,因题而异。
读到这里,读者可以结束阅读了。如有需要,可以再读下面的一个实例。
又:
笔者将给出洛谷一道用批量造数据代码造出数据的题,以作范例。
P r o b l e m : Problem: Problem: 洛谷U289787-三国杀
它的正解为:
#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int A, B;
int aa, bb;
long long al, bl; //老化剩余总攻击力及半兽人剩余总攻击力
long long ans = 0; //存储胜利一方的总剩余血量
queue <string> d; //存储烈士名单
struct hua {
string name;
long long re, at;
bool die = 0; //是否阵亡(0否,1是)
} h[N]; //老化的武将
struct ban {
string name;
long long re, at;
bool die = 0; //是否阵亡(0否,1是)
} b[N]; //半兽人的武将
int main() {
cin >> A >> B;
aa = A, bb = B; //因为最后面输出还要用到没有被更改过的A和B,所以在模拟进程中使用另两变量aa和bb进行操作
for (int i = 1; i <= A; i++) {
cin >> h[i].name >> h[i].re >> h[i].at;
al += h[i].at;
}
for (int i = 1; i <= B; i++) {
cin >> b[i].name >> b[i].re >> b[i].at;
bl += b[i].at;
}
while (aa > 0 && bb > 0) {//回合
for (int i = 1; i <= B; i++) { //先老化打半兽人,这层循环用来寻找半兽人当前未阵亡武将中最先输入的
if (!b[i].die) { //先输入者没阵亡,就要被攻击
b[i].re -= al;
if (b[i].re <= 0) {
b[i].die = 1; //确认已阵亡
d.push(b[i].name);//放入烈士名单
bb--; //半兽人少一将
bl -= b[i].at;
}
break;//既然能进到这个if里面,就说明已有武将被对方攻击,故攻击完后直接退出循环
}
}
for (int i = 1; i <= A; i++) { //后半兽人打老化,这层循环用来寻找老化当前未阵亡武将中最先输入的
if (!h[i].die) { //先输入者没阵亡,就要被攻击
h[i].re -= bl;
if (h[i].re <= 0) {
h[i].die = 1;
d.push(h[i].name);//放入烈士名单
aa--; //老化痛失一将!
al -= h[i].at;
}
break;
}
}
}
if (aa == 0) {//aa=0说明老化没有武将了,所以老化输
cout << "NO\n";
for (int i = 1; i <= B; i++) {
if (!b[i].die)ans += b[i].re;
}
} else if (bb == 0) {
cout << "YES\n";
for (int i = 1; i <= A; i++) {
if (!h[i].die)ans += h[i].re;
}
}
for (int i = 1; !d.empty(); i++) {//不断弹出队首元素并输出
cout << d.front() << " ";
d.pop();
}
cout << endl << ans;
return 0;
}
对于上面的题目,根据标程写出的造数据代码如下(函数顾名思义):
#include<bits/stdc++.h>
using namespace std;
const long long BIG1 = 1e12 * 5;
const long long BIG2 = 1e12 * 3;
const int N = 210;
int A, B;
int aa, bb;
unsigned long long al, bl; //老化剩余总攻击力及半兽人剩余总攻击力
int ans = 0;
char c[100010];
queue <string> d;
struct hua {
string name;
long long re, at;
bool die = 0;
} h[N];
struct ban {
string name;
long long re, at;
bool die = 0;
} b[N];
int choose() { //由于要造名字,名字又有大小写之分,该函数即在1与0之间随即一个数,分别表示取大写与取小写
int a = rand() % 2;
return a;
}
void zao_AB() {
A = rand() % 200, B = rand() % 200;
return;
}
void init() {
A = 0, B = 0;
ans = 0;
memset(h, 0, sizeof h);
memset(b, 0, sizeof b);
}
void zao_laohua() {
for (int i = 1; i <= A; i++) {
//先造名字
for (int j = 1; j <= 10; j++) {
int dd = choose();
char c;
if (dd == 0) {
c = rand() % 26 + 65;
} else {
c = rand() % 26 + 97;
}
h[i].name.push_back(c);
}
//再造血量
h[i].re = rand() % BIG1 + 1;
//最后攻击力
h[i].at = rand() % BIG2 + 1;
}
}
void zao_banshou() {
for (int i = 1; i <= B; i++) {
//先造名字
for (int j = 1; j <= 10; j++) {
int dd = choose();
char c;
if (dd == 0) {
c = rand() % 26 + 65;
} else {
c = rand() % 26 + 97;
}
b[i].name.push_back(c);
}
//再造血量
b[i].re = rand() % BIG1 + 1;
//最后攻击力
b[i].at = rand() % BIG2 + 1;
}
}
void print() {
printf("%d %d\n", A, B);
for (int i = 1; i <= A; i++) {
cout << h[i].name << " " << h[i].re << " " << h[i].at << endl;
}
for (int i = 1; i <= B; i++) {
cout << b[i].name << " " << b[i].re << " " << b[i].at << endl;
}
}
int main() {
srand(time(0));
for (int ii = 1; ii <= 3; ii++) {
sprintf(c, "%d.in", ii);
freopen(c, "w", stdout);
init();
zao_AB();
zao_laohua();
zao_banshou();
print();
// cin>>A>>B;
aa = A, bb = B;
for (int i = 1; i <= A; i++) {
// cin >> h[i].name >> h[i].re >> h[i].at;
al += h[i].at;
}
for (int i = 1; i <= B; i++) {
// cin >> b[i].name >> b[i].re >> b[i].at;
bl += b[i].at;
}
while (aa > 0 && bb > 0) {//回合
for (int i = 1; i <= B; i++) { //先老化打半兽人
if (!b[i].die) {
b[i].re -= al;
if (b[i].re <= 0) {
b[i].die = 1;
d.push(b[i].name);//放入烈士名单
bb--;
bl -= b[i].at;
}
break;
}
}
for (int i = 1; i <= A; i++) { //后半兽人打老化
if (!h[i].die) {
h[i].re -= bl;
if (h[i].re <= 0) {
h[i].die = 1;
d.push(h[i].name);//放入烈士名单
aa--;
al -= h[i].at;
}
break;
}
}
}
sprintf(c, "%d.out", ii);
freopen(c, "w", stdout);
if (aa == 0) {
cout << "NO\n";
for (int i = 1; i <= B; i++) {
if (!b[i].die)ans += b[i].re;
}
} else if (bb == 0) {
cout << "YES\n";
for (int i = 1; i <= A; i++) {
if (!h[i].die)ans += h[i].re;
}
}
for (int i = 1; !d.empty(); i++) {
cout << d.front() << " ";
d.pop();
}
cout << endl << ans;
}
return 0;
}