对拍过程讲解

我们机房有的大佬成天沉迷于学习无法自拔,导致考试时不会对拍,虽然还是$\mathcal{AK}$了

对拍有什么用呢?让我们来看看蓝书是怎么说的:

$\mathcal{1. }$ 当你在打比赛(或考试)时,想出了一个高分代码,但是不保证正确性。

$\mathcal{2. }$ 当你在某OJ上做某题$\mathcal{WA}$了,但是你死活调试不出来,而你又没有数据或者可以下载数据但是数据过大无法找出原因

那么对拍就可以助你一臂之力了~

它可以快速通过你的程序制造的数据,对比你的程序与标程的答案。

如果不同,那么就会停止对拍,这时你就可以通过这组数据找错误了。

如果我们主要是为了找数据,那么一般数据强度小;如果你只想看看你的程序能否$\mathcal{AC}$,那么就往死里开大数据吧

那么这里就讲一下对拍的具体过程~(本过程很大一部分是来自蓝书的)

$\mathcal{Part} \mathcal{1}$对拍具体过程$

$\mathcal{P.S: }这一部分只对具体过程做一个讲解,代码见\mathcal{Part} \mathcal{2}$

$\mathcal{1. }$对你的程序进行操作。 你已经写好了你的程序了,那么现在你要在你的主函数开头加这些话:

freopen("data.in", "r", stdin);
freopen("data.ans", "w", stdout);

作用就是从名为$data$的$in$文件里读入输入数据,然后把程序运行结果输出到名为$data$的$ans$文件里。(当然想取其他名字也可以,只要对应就可以了

运行程序。对于这个程序,我们可以取名为"$sol$"(当然你想取其他名字也行,只要在对拍程序里相对应就行了)。

注意,程序一定要运行一次,必须生成一个$exe$文件!!后面的两个程序也都要运行一次来生成$exe$文件!!

$\mathcal{2. }$对标程进行操作。

标程可以来自考试时你自己写的暴力程序,也可以来自平常做题时从网上找的题解。(千万别找到有防抄袭的代码了)。

同样在主函数开头加上一些话。不过同上面那个程序有些变动。

freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);

作用就是从名为$data$的$in$文件里读入输入数据,然后把程序运行结果输出到名为$data$的$out$文件里。

与上一个程序有一点区别哦,是输出到$out$文件里。当然你也可以让$sol$程序答案输出到$out$文件里,把这个程序的答案输出到$ans$文件里。

可以把这个程序命名为"$bf$"(“暴力程序”的意思)。

$\mathcal{3. }$制造数据。

对拍的原理在开头就已经说了,是比较两个程序的答案。但是你总得有输入才能让它们有输出对吧...

所以你需要写一个数据来制造程序。

写好以后在主函数开头写:

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

它的作用就是把生成的数据输出到名为data的in文件里。

可以把这个程序命名为"$random$"($random$:随机的)。

4.写对拍程序。

现在你已经有了$\mathcal{3}$个文件:

$sol.exe$

$bf.exe$

$random.exe$

那么是时候该用上它们啦~

首先,务必把这三个文件放在同一个文件夹里(放在桌面也可以)。

然后写好你的对拍程序,运行程序即可开始对拍。

注意:对拍程序务必与前三个$exe$文件放在同一个文件夹,否则无法对拍。

那么这就是对拍的整体过程啦~~听懂了吗?

$\mathcal{Part 2 } 对拍过程程序模板$

接下来提供一些模板~

$\mathcal{1. }$ 对拍程序模板

#include <cstdio>
#include <ctime>
#include <cstdlib>
using namespace std;
int main() {
	for (int T = 1; T <= 100; T++) {
		system("random.exe");
		double st = clock();
		system("sol.exe");
		double ed = clock();
		system("bf.exe");
		if (system("fc data.out data.ans")) {
			printf("Wrong Answer!\n");
			return 0;
		}
		printf("Accepted 测试点#%d 耗时%.0lf\n", T, ed - st);
	}
	return 0;
}

$\mathcal{2. }random$ 程序模板

首先提供一个基础模板:

#include<cstdlib>
#include<ctime>
using namespace std;
inline int random (int n) {
    return (long long)rand() * rand() % n;
}
int main() {
    srand((unsigned)time(0));
    //...具体内容...
    return 0;
}

对于任何的$random$程序,这些语句是必加的。

关于这个程序的解释,我们来看看蓝书的句子:

$rand$函数会返回一个$\mathcal{0}$到$\mathcal{RAND}$_$\mathcal{MAX}$的值。

$srand(seed)$函数接受$unsigned int$类型的参数$seed$,以$seed$为“随机种子”,$rand$函数基于线性同余递推式生成随机数,“随机种子”相当于计算线性同余时的一个初始参数。

如果不执行$srand$函数,则种子默认为$\mathcal{1}$。

当种子确定后,接下来产生的随机数列就是固定的,所以这种随机方法也被称为“伪随机”。因此,一般在罪及数据生成程序$main$函数的开头,用当前系统时间作为随机种子。

头文件$ctime(time.h)$包含$time$函数,调用$time(0)$可以返回从$\mathcal{1970}$年$\mathcal{1}$月$\mathcal{1}$日$\mathcal{0}$时$\mathcal{0}$分$\mathcal{0}$秒($\mathcal{Unix}$纪元)到现在的秒数。执行$srand((unsigned)time(0))$即可初始化随机种子。

一般来说用$rand()$函数即可以随机生成一个数了。

至于为什么蓝书单独写一个$random$函数...据说是“综合考虑了操作系统和编译器环境的差异,对$int$范围内的$n$均能正常工作”。

基础模板就讲到这里,接下来免费再提供$\mathcal{4}$个模板哦~(*^▽^*)

$\mathcal{1. }$随机生成整数序列。

#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;
const int N = 100010;
int a[N];
inline int random (int n) {
	return (long long)rand() * rand() % n;
}
int main() {
	srand((unsigned)time(0));
	int n = random(100000) + 1;
	int m = 100000000;
	for (int i = 1; i <= n; i++) {
	    a[i] = random(2 * m + 1) - m;
	}
	for (int i = 1; i <= n; i++) printf("%d ", a[i]);
	return 0;
}/*
此模版会生成n<=10^5个绝对值在10^9之内的整数*/ 

$\mathcal{2.}$ 随机生成区间列。

#include<cstdlib>
#include<ctime>
#include<iostream>
#include<cstdio>
using namespace std;
int m,n;
inline int random (int n) {
	return (long long)rand() * rand() % n;
}
int main() {
	srand((unsigned)time(0));
	scanf("%d%d", &m, &n);
        for (int i = 1; i <= m; i++) {
    	    int l = random(n) + 1;
    	    int r = random(n) + 1;
    	    if (l > r) swap(l, r);
    	    printf("%d %d\n", l, r);
        }
	return 0;
}/*
此模版会生成m个[1,n]的子区间,这些区间可作为数据结构题目的操作序列*/

$\mathcal{3. }$随机生成树。

#include<cstdlib>
#include<ctime>
#include<cstdio>
using namespace std;
int n;
inline int random (int n) {
	return (long long)rand() * rand() % n;
}
int main() {
	srand((unsigned)time(0));
	scanf("%d", &n);
	printf("%d\n", n);
	for (int i = 2; i <= n; i++) {
	    //从2~n之间的每个点i向1~i-1之间的点随机连一条边
	    int fa = random(i - 1) + 1;
	    int val = random(1000000000) + 1;
	    printf("%d %d %d\n", fa, i, val);
	}
	return 0;
}/*
此模版会随机生成一棵n个点的树,用n个点n-1条边的无向图的形式输出,每条边附带一个10^9以内的正整数权值。*/

 $\mathcal{4.}$ 随机生成图。

#include<bits/stdc++.h>
using namespace std;
pair<int, int> e[1000005];//保存数据
map<pair<int, int>, bool> h;//防止重边
inline int random (int n) {
	return (long long)rand() * rand() % n;
}
int main() {
	srand((unsigned)time(0));
	//先生成一棵树,保证连通
	int n = random(100000) + 1;
	int m = random(100000) + 1;
	printf("%d %d\n", n, m);
	for (int i = 1; i < n; i++) {
	    int fa = random(i) + 1;
	    e[i] = make_pair(fa, i + 1);
	    h[e[i]] = h[make_pair(i + 1, fa)] = 1;
	}
	//再生成剩余的m - n + 1条边
	for (int i = n; i <= m; i++) {
	    int x, y;
	    do {
	        x = random(n) + 1, y = random(n) + 1;
	    } while(x == y || h[make_pair(x, y)]);
	    e[i] = make_pair(x, y);
	    h[e[i]] = h[make_pair(y, x)] = 1;
	}
	//随机打乱,输出
	random_shuffle(e + 1, e + m + 1);
	for (int i = 1; i <= m; i++) printf("%d %d\n", e[i].first, e[i].second);
	return 0;
}

注意,你的程序有可能是无法$\mathcal{AC}$的,但是有可能对拍无法将你的程序$hack$掉。

因为数据是随机生成的。而题目往往可能会想某些你没有注意的方向出数据。所以你需要自己单独制造更特殊的数据。

有三种数据可以对树、图进行极端情况下的测试:

$\mathcal{1.}$ 链形数据--有很长的直径。

$\mathcal{2.}$ 菊花形数据--有度数很大的节点。

$\mathcal{3.}$ 蒲公英形数据。

这些数据可以自己想办法构造,所以就不给模板啦~(实际上是自己不会)。

关于对拍的讲解就到这里啦~有疑问可以在下方评论区提出哦~(*^▽^*)。

猜你喜欢

转载自www.cnblogs.com/66ccffxym/p/11397078.html
今日推荐