绪言
-
信息学竞赛中,高手往往能够有很高的正确率,这一方面取决于他们超前的代码能力(传说陈立杰大神在WC2013中,当场怒A平面图神题,代码500+行)。膜拜一下。
-
当然了,除了超前的代码能力之外,他们还非常熟悉一种对拍技术,对他们(包括我)来说,对拍已经是家常便饭了。
-
所以要成为高手的话,你也必须学会对拍。
-
曾经已经写了一个简单的C++版对拍在此,但没有独立引出。今天浅谈一下。本文使用Pascal语言,有些怀旧。
对拍的思想
当你费劲千辛万苦,想出了一个贪心或者动态规划的算法,写完了,样例过了,然后自己出出数据又觉得很麻烦,而且找错误的效率的太低。这个时候或许你的心里是这么想的,假如你有一个自动生成数据的程序,假如你还有一个标准的程序(不管其时间效率是否优秀),假如你还有一个能够控制对拍自动进行的程序,那么一切的一切似乎就很美好。但是,现实是残酷的,这些东西是不会凭空产生
的,都是要你自己写的。
总结起来,4个要点:
1、你自己的程序 2、标准程序 3、生成数据程序
4、控制上面三个程序自动进行对拍的程序
于是乎
原来你只要写一个程序,现在你要写4个程序,这对你的代码能力提出了更高的要求。原来你只要写对一个程序,而现在你要写对4个程序。。。瞬间感觉鸭梨好大o((⊙﹏⊙))o.。
对拍程序(第4个程序)介绍
@echo off //这个可有可无,写了的话就能不让每一句运行的语句显示在屏幕上
:loop // :loop 表示定义了一个循环
make.exe > std.in // make是生成数据的程序,大于符号表示把make的输出结果放到std.in文件里面,std.in存储的是我们自己产生的输入数据
std.exe < std.in > std.out // 小于符号表示将std.in里面的数据输入到std.exe这个程序里,大于表示将结果导出到std.out,std.out是标准程序产生的结果
my.exe < std.in > my.out // my.exe是你自己写的程序,my.out是你的程序的输出
fc my.out std.out // fc 表示比较两个输出文件,如果一样,errorlevel就是0,否则的话,就是1
if errorlevel 1 pause // 表示如果是1的话,就停止程序,因为我们需要去看是哪组输入数据导致了不一样的结果
goto loop //继续循环
无注释版:
@echo off
:loop
make.exe > std.in
std.exe < std.in > std.out
my.exe < std.in > my.out
fc my.out std.out
if errorlevel 1 pause
goto loop
注意,这些东西要写进一个*.bat
的文件里面,*.bat
是windows系统下的批处理程序,上面写语句都是批处理语句。
那里有一个可以计时的bat程序,在此不再赘述。
例题
拿一道比较经典的。
NOIP2001 数的划分
CodeVS-1039 一本通-1304 计蒜客-T2155 LibreOJ-10018
题目
- 题目描述 Description
将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种划分方案被认为是相同的。
1 1 5
1 5 1
5 1 1
问有多少种不同的分法。 - 输入描述 Input Description
输入:n,k (6<n<=200,2<=k<=6) - 输出描述 Output Description
输出:一个整数,即不同的分法。 - 样例输入 Sample Input
7 3 - 样例输出 Sample Output
4
标算
这是一题非常经典的动态规划题目,如果你想出了定义状态f[i][j]表示把i这个数分解成为j个数的方法总数(注意为了防止重复,同时也要求所有数是非递减的)。
那么 f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − j ] [ j ] f[i][j]=f[i-1][j-1]+f[i-j][j] f[i][j]=f[i−1][j−1]+f[i−j][j]
如果从f[i-1][j-1]推得f[i][j],就是f[i][j]里面肯定存在把1分解出来的情况,如果从f[i-j][j]推得,就表示f[i][j]里面肯定没有1的情况,所以f[i][j]的方法总数就包含在这两个子状态的和里面了。
动归程序(第1个程序)
这个可以命名为my.pas
var f:array[0..200,0..6] of longint; i,j,n,k:longint;
begin
read(n,k);
fillchar(f,sizeof(f),0);
f[0][0]:=1;
for i:=1 to n do
for j:=1 to k do begin
f[i][j]:=f[i-1][j-1];
if i>=j then f[i][j]:=f[i][j]+f[i-j][j];
end;
writeln(f[n][k]);
end.
搜索程序(第2个程序)
现在我们要用对拍来验证一个这个程序的正确性了,我们先写一个搜索程序。这个程序可以命名为std.pas。
(此处省略几行)
生成数据的程序(第3个程序)
接下来,我们还需要一个生成数据的程序,这个程序一定要根据题目的要求来生
成数据,一般数据的范围不要太大,这样方便你发现对拍不一致以后迅速找到错
误。程序名可以为make.pas。
程序如下:
var n,k:longint;
begin
randomize(); //这个表示置随机种子,只有写了这句话,才能每次随机产生不同的数。
n:=random(50)+1; //random(50)表示随机产生0-49之间的整数
k:=random(6)+1;
writeln(n,' ',k); //输出n和k的值,因为这个要作为输入
end.
最后一步
最后,把my.pas
编译生成my.exe
,std.pas
编译生成std.exe
,make.pas
编译生成make.exe
,把对拍程序命名为pai.bat
。
把这4个程序(my.exe
、std.exe
、make.exe
、pai.bat
放在同一个路径下),然后点击pai.bat
就开始对拍了,如果发现不同的结果,对拍程序就会中止,此时,你就可以查看std.in
里面那组数组了。
总结
-
对拍是一种技术。
-
对拍是一种能力。
-
对拍不是万能的,但是对拍确实用处很大。
-
熟能生巧,才能掌握好对拍技术,从此你离大牛又进了一步。