ACM/XCPC对拍(Linux/Windows)
前言
心血来潮,整理一手c++对拍,分别是Linux下的脚本对拍和windows下的代码对拍
Windows对拍
windows下的对拍总共三个文件分别是正解(ok.cpp)错解(bad.cpp)和对拍生成数据的文件,对拍的时候只需要运行生成数据文件(beat.cpp)即可。下面给出三个文件示例代码
正解示例代码:ok.cpp
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int a, b;
std::cin >> a >> b;
std::cout << a + b << "\n";
}
错解示例代码:bad.cpp
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int a, b;
std::cin >> a >> b;
if (rng() % 10 >= 1) {
std::cout << a + b << '\n';
} else {
std::cout << a + b + 1 << '\n';
}
}
对拍示例代码:beat.cpp
.\beat是我的对拍存放的目录,可以自行更改,如果存放在你当前开的文件夹下面,代码中的.\beat\都可以不打,默认运行当前文件。
#include <bits/stdc++.h>
int main() {
#define COMPLILE // 是否重新编译,不需要重新编译注释掉就行了
#ifdef COMPLILE
system("g++ -std=c++2a .\\beat\\ok.cpp -o .\\beat\\ok.exe -O3");
system("g++ -std=c++2a .\\beat\\bad.cpp -o .\\beat\\bad.exe -O3");
#endif
for (int i = 0; i < 1000000; ++i) {
std::cout << "Testcase: " << i << '\n';
{
std::ofstream cout("in.txt");
std::uniform_int_distribution<int> dist(2, 1e9);
std::mt19937 mt(std::chrono::steady_clock::now().time_since_epoch().count());
#define rng() dist(mt)
// std::random_device rd;
// #define rng() dist(rd)
cout << rng() % 10 << ' ' << rng() % 10 << "\n";
cout.close();
}
system(".\\beat\\ok.exe < .\\beat\\in.txt > .\\beat\\ok.txt");
system(".\\beat\\bad.exe < .\\beat\\in.txt > .\\beat\\bad.txt");
// getchar(); // 单步回车比较
// system("fc .\\beat\\ok.txt .\\beat\\bad.txt")
if (system("fc .\\beat\\ok.txt .\\beat\\bad.txt")) { // 自动比较
/* 是否将正确结果和错误结果输出在输入文件,方便查看,自行选择是否需要
system("echo ok: >> .\\beat\\in.txt");
system("type .\\beat\\ok.txt >> .\\beat\\in.txt");
system("echo bad: >> .\\beat\\in.txt");
system("type .\\beat\\bad.txt >> .\\beat\\in.txt");
*/
puts("WA!!!");
break;
}
}
}
最后放一个我的文件目录结构
Linux对拍
我的Linux对拍跟windows有点区别,我是使用脚本对拍同时也是用脚本运行文件(当然也可以跟windows一样使用文件对拍)。
下面先放一些linux常用指令, 方便后面对拍代码的理解
clear
功能:清理屏幕,Ctrl+l快捷键具有相同的功能
pwd
功能:显示当前所在的工作目录
cd <path>
功能:改变当前的工作目录
ls [参数] [路径]
功能:显示指定路径下有哪些文件,如果没有路径参数则显示当前工作目录下有哪些文件
man [n] <key> 查看系统的帮助手册
time <可执行文件>
功能:执行一个程序,并记录该程序的执行时间
real 0m0.015s 程序执行的总用时
user 0m0.013s 用户态执行的时间
sys 0m0.000s 内核态执行的时间
real = user + sys + 用户态内存态切换消耗的时间
diff [选项] <可执行文件> <可执行文件>
逐行比较<各文件>。
touch <filename>
功能:创建新文件
rm <filename>
功能:删除文件,删除的文件不经过回收站,而是直接从文件系统中删除,很难恢复,删除时要慎重
cp <src> <<path>/[filename]>
功能:复制文件,可以在复制过程中给目标文件取个新名字
mv <src> <<path>/[filename]>
功能:移动文件,也可以在移动过程中给目标文件取个新名字,并且它还具有重命名的功能
cat <filename>
功能:查看文件内容,它会把文件的所有内容都输出到屏幕上,但不适合用来查看内容比较多的文件
mkdir <dirname>
功能:创建目录
-p 可以创建多级目录
rm -rf <dirname>
功能:强制删除非空目录
cp -r <srcdir> <<path/>[dirname]>
功能:直接使用cp复制目录默认会忽略,需要加上r参数
mv 在移动目录和重命名目录时,不需要加任何参数,直接使用即可
chmod
功能:修改文件权限
用法1:chmod mmm <filename>
每个 m = 4r 2w 1x 都由组成一共有8种情况
0 --- 1 --x 2 -w- 3 -wx
4 r-- 5 r-x 6 rw- 7 rwx
第一m 对应前三个字符 文件的主人的权限(属主)
第二m 对应中间三个字符 与文件的主人同一级用户的权限(属组)
第三m 对应末尾三个字符 其它用户的权限
用法二:chmod +/- r、w、x <filename> 所有用户一起增加或减少某一项权限。
注意:目录文件需要执行权限才能进入,常用的两种权限:644普通文件,755目录文件
timeout [选项] 停留时间 命令 [参数]...
功能:运行指定命令,在指定的停留时间后若该命令仍在运行则将其中止。
code <filename>
功能:使用vsc打开文件,没有就创建
vi/vim <filename>
功能:使用vim打开文件,没有就创建
wc [参数] [路径]
功能:(字数统计)命令打印文件中的行数、字数和字节数。
-l 只打印行数
-w 只打印字数
-c 仅打印字节数
下面介绍以下如何对linux的终端进行配置自定义命令,使得打的代码更短 。
1、在终端输入 sudo vim ~/.bashrc
,按i进入插入模式,从最底行开始编辑
2、ESC进入命令模式,按:键输入wq
保存并退出
3、重新加载配置文件:source ~/.bashrc
实现精简命令提示信息的效果
下面是我的部分配置
alias g++='g++ -std=c++17'
mk() { #创建目录并进入
mkdir $1 && cd $1
}
thsh() { #创建脚本并赋予权限,然后vsc打开
touch $1 && code $1 && echo "#!/bin/bash" > $1 && chmod u+x *.sh
}
thcpp() { #创建cpp文件,并用vsc打开
touch $1 && \
echo "#include <bits/stdc++.h>" > $1 &&
echo "int main() {" >> $1 &&
echo " std::cin.tie(nullptr)->sync_with_stdio(false);" >> $1 &&
echo "}" >> $1 &&
code $1
}
alias cu='cd ..' # 返回上级目录
最后是我的linux对拍脚本(如果没有权限则使用chmod -x beat.sh
设置运行权限)
beat.sh
对拍文件
-std=c++2a
和-O3
可以根据自己选择是否添加
#!/bin/bash
g++ -std=c++2a ok.cpp -o ok -O3
g++ -std=c++2a sol.cpp -o bad
g++ -std=c++2a gen.cpp -o gen
for tt in $(seq 1 100)
do
echo ===== Testcase $tt =====
./gen > in
timeout 1 ./ok < in > okout
timeout 1 ./bad < in > badout
diff -q -b okout badout
if [[ $? != 0 ]]
then
echo \> ok
echo $(cat <okout)
echo \> bad
echo $(cat <badout)
echo \> Example
cat in
./ok < in > ans$(find in* | wc -l) # 创建错误样例正确结果文件
cp in in$(find in* | wc -l) #将错误数据设置为最后一个.in
break
fi
done
gen.cpp
生成数据文件
#include <bits/stdc++.h>
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
using std::cout;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
cout << rng() % 10 << ' ' << rng() % 10 << '\n';
}
run.sh
运行文件
我这里运行的是a.cpp
文件,linux如果不指定则会默认将编译结果储存在a.out
里面。
#!/bin/bash
g++ -std=c++2a sol.cpp
#运行所有测试点
for file in ./in*
do
diff -q -b <(timeout 1 ./a.out < "$file") ans${file#*in}
if [[ $? != 0 ]]
then
echo ===== Error ${file#*in} =====
echo Test:
cat $file
echo ok:
cat < ans${file#*in}
echo bad:
timeout 1 ./a.out < "$file" # 同行输出
else
echo ===== AC ${file#*in} =====
fi
done
# 对拍错误样例
# echo ===== Test Case Error =====
# time ./a.out < in
# 手搓
# echo ===== My =====
# timeout 1 ./a.out
# 运行所有样例 并输出到对应的ans文件
# for file in ./in*
# do
# echo "runing on ${file#*in}"
# ./a.out < "$file" > ans${file#*in}
# done
最后的ac.cpp
和bad.cpp
和Windows一样就不贴了。然后依旧是附一张我的linux对拍目录结构
Makefile执行cpp
CXX := g++ # 编译器选项
CXXFLAGS = -std=c++2a # 编译器参数
OBJS = sol.cpp
TARGET := sol # 此处添加最后生成的可执行文件名
all:$(TARGET)
@./$^ <in1
$(TARGET):$(OBJS)
@(CXX) $(CXXFLAGS) -o $@ $^
@rm -rf *.o
数据生成
最后放一下图和树的数据制造代码
{ // 随机生成图
int n = rng();
std::vector<std::pair<int, int>> h;
std::vector<std::vector<int>> g(n + 1, std::vector<int>(n + 1));
for (int i = 2; i <= n; ++i) {
int x = rng() % (i - 1) + 1;
g[x][i] = 1;
h.emplace_back(x, i);
}
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) if (!g[i][j] && rng() % 2) {
g[i][j] = 1;
h.emplace_back(i, j);
}
}
cout << n << ' ' << size(h) << "\n";
for (auto [a, b] : h) {
cout << a << ' ' << b << ' ' << rng() << "\n";
}
}
{ // 随机生成树
int n = rng();
cout << n << '\n';
for (int i = 2; i <= n; ++i) {
cout << rng() % (i - 1) + 1 << ' ' << i << "\n";
}
}