首页 > 其他分享 >OI 中的小技巧(工具)

OI 中的小技巧(工具)

时间:2024-05-09 22:45:19浏览次数:11  
标签:文件 技巧 OI ++ test station cpp 工具 main

OI 中的小技巧(工具)

目录

ref

https://zhuanlan.zhihu.com/p/494668063

bash 基础语法

可执行文件

在 NOI Linux 或其它 Linux 环境下打开终端,可以在终端中输入命令:

you@localhost:~/test$ g++ a.cpp -o main
you@localhost:~/test$ ./main
1 2
3

这里我们编译了 a.cpp 文件为 main,运行,发现它正确实现了计算 \(a+b\) 的功能。g++./main 是可执行文件,在一条命令的开端,后面紧跟的都是参数。g++ 在环境变量中,main 在当前目录下,在 bash 中运行不在环境变量中的可执行文件需要写出其路径,最简单的方法就是写为 ./main. 表示当前目录(.. 表示上一级目录),/ 是文件夹名与文件名之间的分隔符。以上内容建议百度“Linux 命令行入门”进一步了解。

变量

bash 中可以定义变量。

you@localhost:~/test$ a=1
you@localhost:~/test$ echo ${a}
1

a=1a 是变量名,1 这里当作一个字符串。读取变量 a 的内容,写作 ${a}同样这些东西建议百度了解

管道

bash 中存在名为管道的东西,作用是将上一个命令的标准输出传递给下一个命令的标准输入,符号是 |

you@localhost:~/test$ echo 1 2 | ./main
3

例如可以 echo 1 2 输出 1 2,将其传递给 ./main 作为它的标准输入。更有用的地方是,部分命令如 diff grep 可以通过指定文件名为 -(或其它,可以传递参数 --help 查看)以读取标准输入作为文件内容。因此可以干出这样的事情:

you@localhost:~/test$ echo 1 2 > in
you@localhost:~/test$ echo 3 > ans
you@localhost:~/test$ ./main < in | diff - ans
you@localhost:~/test$ echo 4 > out
you@localhost:~/test$ ./main < in | diff - out
1c1
< 3
---
> 4

Linux 文件名可以无后缀名。这里依次创建了三个文件。第一次运行 ./main,输入 1 2 输出 3,与文件 ans 比较相同;第二次运行 ./main,输入 1 2 输出 3,与文件 out 比较不同,输出了错误信息。

if-else

大概这样写:if {{condition_command}}; then {{echo "Condition is true"}}; fi

注意:{{condition_command}} 返回 0 的时候进入后面的语句块,返回 0 一般表示这个命令成功运行。这一点与其它语言不同。

可以百度或者 help if 获得更多帮助。

for

for {{variable}} in {{item1 item2 ...}}; do {{echo "Loop is executed"}}; done

for {{variable}} in {{{from}}..{{to}}..{{step}}}; do {{echo "Loop is executed"}}; done

for {{variable}} in */; do (cd "${{variable}}" || continue; {{echo "Loop is executed"}}) done

可以 sudo apt install tldr 然后 tldr for 获得更多帮助。

while

while {{condition_command}}; do {{command}}; done

写在同一行的时候需要分号。do 前面换行可以去掉 do 前分号。done 前换行可以去掉 done 前分号。for 同理。

bash 应用

time

作为内置命令时,time 后面直接加一条命令可以测量其运行时间。

you@localhost:~/test$ time ./main
1 2
3

real    0m0.383s
user    0m0.002s
sys     0m0.000s

作为一个可执行文件 /usr/bin/time(等价于 /bin/time)时,也是同样用法,但是输出不同:

you@localhost:~/test$ /bin/time ./main
1 2
3
0.00user 0.00system 0:02.34elapsed 0%CPU (0avgtext+0avgdata 3576maxresident)k
0inputs+0outputs (0major+135minor)pagefaults 0swaps

只需要知道评测时看的是 user time。测量空间时,使用 /bin/time -v

you@localhost:~/test$ /bin/time -v ./main
1 2
3
        Command being timed: "./main"
        User time (seconds): 0.00
        System time (seconds): 0.00
        Percent of CPU this job got: 0%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.07
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 3424
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 134
        Voluntary context switches: 2
        Involuntary context switches: 0
        Swaps: 0
        File system inputs: 0
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

Maximum resident set size (kbytes): 3424 是所测量的。

ulimit

修改终端的资源限制,其中最有用的是修改栈空间:ulimit -s unlimited 表示无线栈空间。unlimited 可以改为任意一个 KB 为单位的数字,如 ulimit -s 1048576 使得栈空间为 1G。

批量过样例

想象你正在参加一场 OI 比赛,本题的英文名为 station,所以你编写了 station.cpp 作为提交的代码,现在想要测试之。已经去掉了 freopen。

可以编写 test.sh

#!/bin/bash
ln -s ../../down/station/* .
g++ station.cpp -o station -std=c++14 -O2
for i in {1..2}; do
	time ./station <station$i.in | diff - station$i.out -Bsbq
done

第一行称为 shebang,指定使用 bash 解析这个文件。

第二行将所有样例文件链接到当前目录,可以搜索一下什么是软链接。这里将存放在 ../../down/station/ 下形如 station1.in/out 一共两祖样例链接到当前目录。

第四行是 for 循环,\(i\) 从 \(1\) 到 \(2\)。

第五行,首先 time ./station <station$i.in 运行 station,将输出通过管道传递给 diff,与答案文件进行比较。对于 diff,一共传递了四个选项:-b 选项意在忽略行末空格,-B 忽略文末换行(实际上 -Bb 忽略了更多东西,diff --help|grep '\-b' 查看),-s 选项指出在文件相同时输出 ... are identical-q 选项指出在文件不同时仅输出 ... differ。本题输出文件很大,如果输出量少,可以去除 -q 选项观察不同之处。diff 还拿到了两个文件,文件 - 是标准输入,从管道中获得;文件 station$i.out 是样例输出。这里 station$i.out 实际上是个 format string,将 $i 替换成一个数字,写成 ${i} 也对,这里变量名只有一个字母,也可以写 $i

第六行结束 for 循环。现在你就写了一个 OI 中可以用的工具脚本。

写完以后给他添加执行权限:chmod u+x test.sh。然后 ./test.sh 运行。

或者可以去掉第一行的 shebang,使用 bash test.sh 运行,或者 . test.shsource test.sh。保留第一行 shebang,make test(此时目录下不要有名为 test 的文件,包括代码),接着 ./test。首选 chmod

运行效果:

$ ls ../../down/station/
station1.in  station1.out  station2.in  station2.out
$ vim test.sh
$ chmod u+x ./test.sh
$ ./test.sh
station.cpp: In function ‘void work(int, int, int, mint)’:
station.cpp:153:10: warning: structured bindings only available with ‘-std=c++17’ or ‘-std=gnu++17’
  153 |     auto [u, w] = q.front();
      |          ^
Files - and station1.out are identical

real    0m0.016s
user    0m0.001s
sys     0m0.012s
Files - and station2.out are identical

real    0m0.024s
user    0m0.002s
sys     0m0.017s

花絮:freopen 开关

你用这个 test.sh,需要去掉 freopen。OI 比赛怎么能随意去掉 freopen 呢?考虑复制一下文件:

#!/bin/bash
g++ station.cpp -o station -std=c++14 -O2 
for i in {1..2}; do
  cp station$i.in station.in
  time ./station
  diff station.out station$i.out -Bsbq
done

我常用的还有另一种方法是将 freopen 写成这样:

#ifndef NF
  freopen("station.in", "r", stdin);
  freopen("station.out", "w", stdout);
#endif

然后在 g++ 这一行的参数(编译选项)中加入 -DNF 表示 define 名为 NF 的 macro,以去掉 freopen。之所以名字是 NF,因为 do not finish 的缩写是 DNF。

对拍器

当前我们有两份代码 main.cppbf.cpp,前者因未知原因挂了,后者是朴素程序。写了一个 dt.py 用于生成一组数据,输出到标准输出(可以用 c++ 写 data.cpp 编译为可执行文件,这里是用 python 写的)。现在可以进行对拍:

#!/bin/bash
cnt=0
g++ main.cpp -o main -O2 -DNF -g
g++ bf.cpp -o bf -O2 -DNF -g
while :; do
  echo Testcase $((++cnt)) is running...
  ./dt.py > in
  ./bf <in >ans
  ./main <in >out
  if diff out ans -Bbq; then :
  else echo WA; break; fi
done

这里,: 是一个无效果的命令,始终返回表示”成功运行“的 0。while :; 就是无线循环。

\(cnt\) 指示了当前是第几组数据,应该以 $((++cnt)) 的方法使它自增,小心其它方法可能使得 \(cnt\) 变成字符串,可以自己尝试。

第 10 行 if diff out ans -Bbq; then : 这里比较两个输出文件,相同时返回 0,此时没有操作。否则,输出 WA,并中断循环。此时可以查看输入文件 in,输出文件 out,答案文件 ans。去掉 diff 的 -s 是个人喜好,看着加。

备份工具

有时候可能有保存这场比赛所有代码的需求。这样可以将暴力代码留作备份并删掉,而且检查代码是否编译通过,还有方便最终提交,最重要的是防止误删(曾经试过将一份暴力代码 copy 成正解代码,导致正解代码被覆盖,无法找回)。

可以编写 backup.sh

#!/bin/bash
tim=$(date|awk -F " " '{print $4}')
dst=$(dirname $0)/tmp/${tim//:/-}
echo start backup at ${tim//:/-}
mkdir $dst
for p in seq butterfly hoshi; do
  src=$(dirname $0)/$p/$p.cpp
  if [ -e $src ]; then
    cp $src $dst
    if g++ $src -o $dst/main -O2 -std=c++14; then
      echo Problem $p: ok \(remember to check freopen via warnings\)
    else
      echo Problem $p: CE
    fi
  else
    echo Problem $p: not found
  fi
done
rm -f $dst/main

这个有一点长,会有一点难记。可以转写成 python 版本,会比较好写,就是字多一点。下面是逐行解释:

第一行 shebang。

第二行,提取了当前的时间,data 返回 Thu May 9 21:21:34 CST 2024 这样一个东西,这东西当然不能直接用作文件名。所以使用 awk -F 分割了这个字符串,取出第 4 项(0-indexed),写法可参考或百度。这时 tim=21:21:34

第三行,dst 是 destination,指出备份代码存放在 dst 中。$0 是脚本自己所在的路径,dirname 取出这个路径的文件夹名。${tim//:/-} 这里,因为不能以冒号做文件名,将冒号替换成连字符,和 vim 替换比较像,注意 tim 后面两个正斜杠表示全部替换。dst 这时就是 ./tmp/21-21-34,代码存放到这个路径下。

第四行确认一下 tim 是对的,因为有些系统的时间取出来可能是第 3 项、第 5 项,因为中文的原因。自己枚举一下。

第五行新建文件夹。

第六行遍历 p 为题目名称,这次比赛有三道题,分别是 seq、butterfly、hoshi。

第七行找出将要备份代码的路径,记为 src,是 source 的缩写。

第八行,[ -e $src ] 判断文件 $src 是否存在,注意两边空格不能扔掉。if 判断之。

第九行,文件存在,复制到 dst。

第十行,进行编译测试,只保留 -O2 -std=c++14 这两个最基本的。

第十一行,编译通过,输出信息。这时如果有 freopen 存在,会对 freopen 报”无返回值“的警告,观察你的 freopen 对不对。如果没有就惨了。如果想进一步检查,可以使用 grep:grep $p $src --color=auto,看他找不找的到。找不到会无输出或者只输出一个,此时你文件名写错;找到了有刺眼红色提醒。对于这个 freopen 检查,因为传参数有很多双引号在,其实是很不好写的,建议别写。如果文件名写对了是很整齐的。

第十三行,发现编译错误,及时提醒。

第十六行,未找到文件,输出未找到。

第十九行,清理刚才编译的可执行文件。

然后和 test.sh 一样赋予它执行权限,然后就可以运行了。

考场上 .vimrc 和 backup.sh 两个写完需要 10~20 分钟,取决于手速,如果发现写的慢了去读一下题缓解紧张。

makefile

make 是一个古老的构建工具,帮助我们构建项目。在 OI 中可以帮我们编译,做到一个一键编译的效果,既有灵活性又少打很多字符。

基础语法

以下内容都写在当前目录下名为 makefile 的文件中。

目标文件名: 依赖文件1 依赖文件2
	命令

注意命令前面是一个 TAB。

如:

bf: bf.cpp
	g++ bf.cpp -o bf -O2 -DNF -g
main: snow.cpp
	g++ snow.cpp -o main -O2 -g -fsanitize=undefined,address -DNF
snow: snow.cpp
	g++ snow.cpp -o snow -O2 -g -std=c++14 -DNF -Wall -Wextra -Wconversion

终端输入 make snow 不是造雪,而是编译可执行文件 ./snow,可以 make snow && ./snow 一个组合技。make bf 也是,make main 也是,mainsnow 的区别是调试选项的力度。调试时使用 main 版本,测样例使用 snow 版本。优势在于文件没有修改时,不会触发重新编译,节省时间。

关于变量:makefile 的变量在每个目标外面,大概这样写:

SRC = snow.cpp bf.cpp

和 bash 变量不一样的是等号两边可以加空格,访问变量是 $(SRC) 圆括号,转义美元符号是 $$。这里面 string 的双引号规则和 bash 一样(不用写)。这个变量其实因为开在全局,没得修改,是个常量,所以应该大写。

$() 圆括号里面可以调用 makefile 的函数,调用 bash 函数是 $(shell ls) 这样子。

传统题写法

main: seq.cpp
	g++ seq.cpp -o main -DNF -g -O2 -DLOCAL -fsanitize=undefined,address
%: %.cpp
	g++ $< -o $@ -DNF -g -std=c++14 -Wall -Wextra -Wconversion -Wshadow

main 就是调试力度很大的版本,并且 make 调用如果没有任务参数,默认调用第一个,可以写 make && ./main 编译并运行,make && gdb main -q 编译并进入调试状态。

%.cpp 是一个通配符,将 %.cpp 编译成去掉 .cpp 的可执行文件,每个匹配到的文件都是分开的规则。$< 表示第一个依赖文件(也只有一个),$@ 表示目标文件名。然后后面跟着一大堆参数就是编译警告拉到很满,可以参考一下,输出一车警告的,可以仔细看,不看也没关系。反正真正的编译检测是 backup.sh 做的。

这样以后,对拍器第三、四行可以写 make -j main bf-j 表示两个编译任务并行,会快),test.sh 编译部分改成 make station,少写很多的。每个题的 makefile 都不一样,需要复制几次。

交互题写法

main: stub.o toxic.o
	g++ $^ -o $@ -fsanitize=undefined,address
%.o: %.cpp toxic.h
	g++ $< -o $@ -I. -c -DNF -O2 -g -DLOCAL

这一题提交代码是 toxic.cpp,交互库是 grader 交互库 stub.cpp,有共同头文件 toxic.h。首先用 g++ -c 将文件编译为 .o 但是不链接,参数 -I. 指出头文件在当前目录(也可以是别的自己写),后面是常规的编译选项。构建 main 需要 toxic.ostub.o,将他们最终链接输出成可执行文件。$^ 是所有依赖文件。注意 -fsanitize=undefined,address 写在链接这一步。

另记:不应该把交互 grader 写在头文件里……另外交互题最好把自己的全局变量扔到 namespace 里面,防止对面交互库神操作撞名。

打扫文件

希望 make clean 删掉垃圾文件,包括可执行文件等。这样写:

.PHONY: clean
clean:
	rm -f main $(patsubst %.cpp, %, $(wildcard *.cpp))
	rm -f $(patsubst %.cpp, %.o, $(wildcard *.cpp))

wildcard *.cpp 是通配符匹配,找到所有 .cpp 文件。patsubst 是字符串替换,将 %.cpp 替换成 %,也就是所有可能的可执行文件名。还有一个 main 也一起删掉。如果是交互题生成了 .o 也删掉。

还可以加这四条,把输入输出数据删了(数据刚才说是是软链接的,所以真实的数据不会有事):

.PHONY: clean
clean:
	rm -f main $(patsubst %.cpp, %, $(wildcard *.cpp))
	rm -f $(patsubst %.cpp, %.o, $(wildcard *.cpp))
	-find . -name '*~'|xargs rm -rf
	-find . -name '*in'|xargs rm -rf
	-find . -name '*out'|xargs rm -rf
	-find . -name '*ans'|xargs rm -rf

.PHONY: clean 表明 clean 是个伪目标,即我们不是真的要生成名为 clean 的文件。还有另一个常见伪目标 ALL,这个才是 make 无任务参数调用的,默认是第一个而已。

前面有 - 号表示发生错误时(如找不到文件)忽略继续。rm -f 也会干这样的事。

但是在赛场上你是不会想到删文件的,所以不用背。

结合 vim

vim 的命令模式也有 :mak[e] 命令(中括号表示可以省略),就是调用外面的 make,关键是如果编译错误,vim 会将光标定位到第一个出错的地方,可以 :cn[ext] 到下一个地方。不知道考场上能不能用。

标签:文件,技巧,OI,++,test,station,cpp,工具,main
From: https://www.cnblogs.com/caijianhong/p/18183222

相关文章

  • 洛谷 P1031 [NOIP2002 提高组] 均分纸牌 题解
    题目简述有$N$堆纸牌,编号分别为$1,2,\ldots,N$。每堆上有若干张,但纸牌总数必为$N$的倍数。可以在任一堆上取若干张纸牌,然后移动。移牌规则为:在编号为$1$堆上取的纸牌,只能移到编号为$2$的堆上;在编号为$N$的堆上取的纸牌,只能移到编号为$N-1$的堆上;其他堆上取的纸牌,可......
  • P3350 [ZJOI2016] 旅行者
    P3350[ZJOI2016]旅行者分治+最短路网格图可以想到分治。每次将长边分为两半,处理越过中线的询问。那么就可以枚举中线上的每个点更新答案,经过\(x\)的路径更新\((u,v)\)就是\(dis_{u,x}+dis_{x,v}\)。每次预处理中线上每个点的单源最短路即可。设\(S=nm\),复杂度\(O(S\sq......
  • 洛谷 P1012 [NOIP1998 提高组] 拼数 题解
    题目简述设有$n$个正整数$a_1\dotsa_n$,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。题目分析定义设$X$为数字$x$的字符串形式。$A+B$表示字符串$A$和字符串$B$相连组成的字符串。思路既然要构造最优解,显然如果有不优的情况的话,就需要对序列进行......
  • P3842 [TJOI2007] 线段
    洛谷-题目链接[TJOI2007]线段提示我们选择的路线是(1,1)(1,6)(2,6)(2,3)(3,3)(3,1)(4,1)(4,2)(5,2)(5,6)(6,6)(6,4)(6,6)不难计算得到,路程的总长度是24。代码代码#include<bits/stdc++.h>usingnamespacestd;constintN=2e4+5......
  • P4301 [CQOI2013] 新Nim游戏 线性基
    P4301[CQOI2013]新Nim游戏线性基题目链接题意:两个人进行游戏,有\(n\)堆火柴,每堆有若干根,在第一个回合中,双方可以直接拿走若干个整堆的火柴,可以一堆不拿,但不可以全部拿走。接下来的回合进行\(Nim\)游戏。现在你是先手,第一回合如何拿才能保证获胜,并且让第一回合拿的数量尽......
  • Nmap使用技巧总结
    ## NMap使用技巧总结一、主机发现1.全面扫描/综合扫描nmap-A192.168.1.1032.Ping扫描nmap-sP192.168.1.1/243.免Ping扫描,穿透防火墙,避免被防火墙发现nmap-P0192.168.1.1034.TCPSYNPing扫描nmap-PS-v192.168.1.103nmap-PS80,10-100-v192.168.......
  • Mapster工具类
    一、使用Mapster//安装nuget包Install-PackageMapster二、封装工具类///<summary>///Mapster映射帮助类-基础使用///Mapster版本7.3.0(或7.2.0)///ASP.NET使用时可直接services.AddMapster();///</summary>publicclassMapsterHelper{#region实体映射......
  • 篮球技巧
    内线内线进攻先给来几下身体对抗打进在使用假动作;内线起三步对抗,第一步靠近防守人(稍微留有间隙),第二步卡住防守人的篮筐侧脚(蓄力并进行对抗),起跳完成投篮或者打板调动防守人之后,多使用转身加上下步终结投篮下球转身抛投下球转身虚晃上下步内线卡主前侧,转身反跑要......
  • kubectl工具
    1.k8s集群的命令行工具kubectl1)语法格式: getnodegetnodesgetnodek8snode1 #k8snode1是一个node名字2)帮助命令kubectl--help具体查看某个操作:kubectlget--help3)基础命令  4)目前使用到的命令  ......
  • 原型工具分析及对比
    主流的原型设计工具包括Axure、墨刀、Sketch、AdobeXD、Figma等。下面我将对这几种工具进行简要比较,并重点介绍其中Axure、墨刀及对比。Axure的特点:1.Axure是一款功能强大的原型设计工具,适用于高保真原型设计和交互式原型制作。2.支持复杂交互、动画效果和数据驱动功能,可用于......