信息竞赛编题造数据教程

由来:笔者在编一道叫做 三国杀 的题时,因为作者不会造数据,所以他在网上断断续续翻找了整整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和下文的 streamstdin,起到从 filename指定的文件中读取数据作为程序的输入的作用;而“只写访问”亦是如此。其配合下文的 sstreamstdout,起到将程序的输出存储到 filename所指定的文件中。

stream表示需要被重定向的文件流。依上文,我们主要使用 stdinstdout

现在,让我们看回最上面的两行代码的例子。通过上面的解释,我们可以得到第一行表示从一个叫 “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

现在,我们已经了解了上述造数据代码的作用,让我们运行代码,看看效果。(注意:通常情况下,代码应当是在运行后未经任何认为操作就显示结束。)

先将代码放入一个文件夹(这样可以方便到时候压缩,如果放在桌面很容易与桌面其它文件混乱)

1

运行:

2

我们便得到了 10 10 10 个完整的数据(这里随机打开一组以验证数据的准确性。):

3

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;
}

完结撒花~~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值