一些经验之谈。
I. 学会使用 Linux 虚拟机
在日常生活中,我们使用的电脑系统通常是 Windows,线下考场中的电脑也几乎都使用 Windows 系统,而 Linux 系统我们一般很少接触到。不幸的是,几乎所有的 OI 比赛的评测机和 OJ 所使用的系统都是 Linux。同一份代码在不同的系统上的运行结果可能是不同的,这就可能导致:你在考场上辛辛苦苦写了几小时代码,并且它成功通过了所有样例,结果 CCF 给你的评测结果是爆零。
为了避免这种情况,你应该在考试之前学会使用 Linux 系统。你可以在你的电脑上安装一个 NOI Linux —— 这是 CCF 提供的专供 OI 使用的 Linux 系统,所有 CCF 举办的线下比赛所提供的电脑都应该会提供这个东西,所以你在比赛时也可以使用它。
在你比赛写完代码之后,你应该把代码复制到虚拟机上重新测试一遍,如果没有问题,才能保证你的代码不会因为“非智力因素”出错。
II. 不要 using namespace std; & 注意防止重名
using namespace std;
诚然是一个非常方便的语句,这导致它受到了许多 OI 新人的青睐。事实上我以前也很喜欢使用这个语句,并且对网上看到的那些大佬所说的“千万不要使用 using namespace std;
”进行了忽视,直到我自己差点在 CSP-S 2022 中因为这个原因爆零,才深刻地认识到了这玩意的险恶之处。
using namespace std;
的优点在于,它可以一次性帮你把几乎所有需要的库函数引入进来,但这也是它的缺点:它引入的函数实在是太多了。std
中包含了浩如烟海的函数,其中很多你连听都没听过。而众所周知,你自己的变量名/函数名等名称若是和库函数重名了,容易造成不可预料的后果,最常见的就是 CE,然后爆零。由于 std
中的函数很多,你的重名机会也大大增加了,这就让你可能会爆零。
我在 CSP-S 2022 的 T2 中给一个数组取名为 bzero
,这看起来是一个很正常的名字,没想到它居然也是 std
中的一个库函数名。更为致命的是, Windows 系统下编译,这种问题既不会报错,也不会警告,这就让我差点爆零了。为什么是“差点”?因为我很幸运,我无意把这个数字定义成了局部变量而不是全局变量,避免了因为这种非智力因素爆零。后来我兴奋地把这件事发到了洛谷上,然后发现一位老哥和我犯了同样的错,用了同一个变量名。不过他就没有这么幸运了,他定义的是全局变量,然后他就爆零了(
说回正题,如果不能写 using namespace std;
,我们该怎么使用 std
中的函数呢?
有两个方法:一是在程序的开头写上 using std::function_name
,其中 function_name
是你要使用的语法名字,之后你就可以在程序中随意使用了。这样写不仅可以引入单个函数,也可以引入一个 C++ 容器的所有成员函数。比如如果写 using std::queue
,就可以同时把 queue.push()
queue.front()
等函数都引入进来。二是在你要使用这个语法时,在它前面紧挨着的地方写上 std::
,比如 std::cout << "Hello" << std::endl;
。相比起前一种方法,这种方法每使用一次次只能引入紧挨着它后面的一个常量/函数,下次你如果还要使用,你还得再写一次,而第一种方法是一劳永逸的:只要在程序开头写一次,之后就不用再写了。
不过要说明:即使你像我说的做了种种防止重名的方法,你还是有可能重名。所以你讲那么多干什么?一个很可能的原因是你的名称和 C 语言的库函数重名了。(这次敌人终于不来自 std
了。)众所周知,C++ 向下兼容了所有 C 的函数,而这些来自 C 的函数在使用时并不用 std
,比如你不需要把 printf()
写成 std::printf()
。这就导致我上面说的那些方法对这种错误不起作用,你要是不小心把变量名/函数名写得和 C 语言中的库函数名重名了,照样会 CE。一个出名的例子是 <cmath>
库中的 x0
之类的变量名。
那么这种问题要这么防范呢?这时候还是得使用我们预防非智力因素错误的超级大法:在 Linux 系统下编译,就像我在第一条建议中说的那样。因为这些错误虽然在 Windows 下不会显现出来,但是在 Linux 下编译是会报错的。你只要把程序修改得在 Linux 系统下也不会报错,它大概就真的没有问题了。
III. 学会对拍
在学习对拍之前,要先了解 fc
的使用。它是 Windows 下比较文件的命令行工具。使用它,我们可以很方便地判断两个文件是否相同。它可以在命令行(cmd)下使用,也可以把它写到 C++ 的 system
函数中使用。下面我将具体介绍它的使用方法。
- 在 cmd 下使用。
按下 Win + R,输入 "cmd",打开命令提示符。把要比较的两个文件放在同一文件夹下,在命令提示符中使用 cd 语句切换到这个目录。然后就可以使用 fc 了,它的格式如下:fc filename1 filename2
,注意文件名包含后缀。如果两个文件相同,命令提示符会显示“找不到差异”,否则会显示第一次找到差异的地方。
- 在 C++ 中使用。
在 C++ 中使用的关键是system()
函数。这个函数可以执行一些命令行工具,方法就是直接把语句粘贴进去就行了。比如想比较 1.txt 和 2.txt 两个文件,就可以写成system("fc 1.txt 2.txt");
—— 引号里面的东西和直接写在 cmd 里的是一样的!屏幕上显示的东西也和命令提示符是一样的,这里就不在赘述了。
(这只是 fc 的基本用法,它的更多用法可以自行上网查阅。)
我们已经知道了 fc 语句的用法,那么它在 OI 中有什么用呢?首先,它可以比较你的输出和标答是否相同。如果输出规模较小,你通常可以使用瞪眼大法,用肉眼比对。不过用人眼比较终究是有风险的,而且如果输出规模比较大,瞪眼大法就根本不可行。这时候 fc 就派上用场了:你可以把答案输出到一个文件里,然后把这个文件和标答比较,就能准确地知道是否相同了。
fc 的另一个用法就是我们重点要讲的——对拍。
什么是对拍?对拍是一种检验程序是否正确的方法。有的时候你的程序通过了所有的样例,你却还是会担心样例太弱了,无法查出程序的一些问题,这时候就可以用到对拍了。
那么究竟如何对拍呢?对拍需要四个 cpp 文件:一个数据生成器(generator),一个需要检验正确性的程序,一个能保证正确性的程序,以及一个用于执行对拍的程序。
啥是“能保证正确性的程序”(下面简称为暴力程序,虽然这个程序不一定是暴力)?你或许会问。如果我能写出一个能保证正确的程序,我还用得着对拍吗?事实上,这个程序并不是指能 AC 的程序,而是指能保证它的输出结果是正确的,但是时间复杂度太高,会导致 TLE 的程序,比如说暴力程序。如果直接提交这个程序,可能会因为 TLE 而只能得到部分的分数,但是我们在本地使用它时,可以接受它的时间复杂度稍微大一点(当然也不能过于大了,如果你的程序一天都跑不出结果那也是万万不行的)。所以对拍的原理就是:用数据生成器生成数据,让两个程序分别运行。其中暴力程序输出的是正确的结果,把这个正确的结果和待检测程序的结果相比较,就能知道待检测程序是否正确的。
当然,只测一次往往是不能验证正确性的,所以我们通常将对拍程序写在一个循环里,如果运行多次都没有发现差异,就说明待检测程序是正确的。至于要循环多少次,当然是循环得越多越能保证正确性,循环次数越多越好。不过这通常受限于你暴力程序的运行时间,如果你的暴力程序运行一次要十几甚至几十秒,那你恐怕也对拍不了几组。
对拍代码如下:
#include <bits/stdc++.h>
int main() {
// For Windows
// 对拍时不开文件输入输出
while (1) // 也可以写成 for 循环中指定组数
{
system("gen > test.in"); // 数据生成器将生成数据写入输入文件
system("test1.exe < test.in > a.out"); // 获取程序1输出
system("test2.exe < test.in > b.out"); // 获取程序2输出
if (system("fc a.out b.out")) {
// 该行语句比对输入输出
// fc返回0时表示输出一致,否则表示有不同处
system("pause"); // 方便查看不同处
return 0;
// 该输入数据已经存放在test.in文件中,可以直接利用进行调试
}
}
}
(这一段代码来自 OI wiki)
IV. 记得文件读写怎么写
文件读写就是 freopen
,每年都有因为这个写错或者不写而挂掉的。CCF 举办的比赛中都要求写文件读写,千万不要忘记了,否则你将会失去这一题全部的分数。
文件读写的格式如下:
int main()
{
freopen("filename.in", "r", stdin);
freopen("filename.out", "w", stdout);
/* Your code*/
fclose(stdin);
fclose(stdout);
return 0;
}
其中,freopen
的两行是必须写的,fclose
可以不写,但是可能会有小概率出问题,所以最好还是写上。(其实关于 fclose
要不要写我也不是很懂。)
文件读写的作用不仅仅是让你不爆零,它在代码调试的过程中也是非常有用的。比如样例太大的时候,你无法通过 Ctrl + C/V 复制粘贴到程序的运行窗口中,这时候你就只能通过文件读写读入样例。所以请你一定要记得文件读写怎么写,否则你连样例都测试不了。
V. 注意 C++ 版本
根据《关于NOI系列活动中编程语言使用限制的补充说明》,C++ 程序编译默认采用的语言标准为 C++14。而我们使用的 Dev 编译采用的默认版本通常比较老旧(通常是 C++98),导致我们无法使用一些较晚版本更新的语法,或者让我们无意间使用了过于老旧的、在新版本中已经被废除看的语法而无法察觉。这就要我们手动给 Dev 选择高版本的 C++。
怎么给 Dev 选定 C++ 版本呢?点击工具-编译选项-编译器,找到“编译时加入以下命令”,点亮这行文字左边的√,在下方写上 -std=c++14
,即可把版本设为 C++14。这行语句中的 c++14
也可以替换成其它的 C++ 版本,比如 -std=c++11
就能把版本设为 C++11。
设置版本之后,许多较新的语法都可以使用了。比如大名鼎鼎的 mt19937
——它于 C++11 中被更新进来,如果你不会手动选择 Dev 的 C++ 版本,你就无法使用它,而只能使用老旧的 rand()
函数。曾经有一位同学让我把一道题的数据发给他,我就把一个用 mt19937
写的 generator 发给了他,但是由于他不知道如何选择 C++ 版本,他就不会运行这个 generator 了,可见学会手动选择 C++ 版本的重要。
这里我给大家推荐一篇洛谷日报:link,它总结了一些 C++ 的实用语法,其中大部分语法都在 C++98 以后,C++14 及以前的版本里,你可以放心地在考场上使用它——只要你会手动选择 Dev 的 C++ 版本。其中许多语法能极大地便利代码的书写。
结尾
本篇文章开始写于 2023/01/24 晚上8点左右,耗时大约 2.5 小时写完。
Upd on 2023/01/25:新增加了第 V 条。
标签:std,OI,使用,程序,C++,fc,线下,版本,注意事项 From: https://www.cnblogs.com/dengstar/p/17069134.html