Caiwen的博客

OI中常见错误及策略

2023-10-20 02:10:00

IO

  • 注意模数看好了不要写错(建议拿鼠标复制下来)

  • 看清楚输出的是 YESNO 还是 YesNo 还是 YE5N0 (测大样例的时候应该能发现)

  • 注意有没有把调试信息删除

补充

事实上,你可以通过使用 std:cerr<<"..." 来输出调试信息,这样即使你忘记去掉了,也不会把调试信息输出到输出文件里。

但还需要注意, cerr 比较慢

综上,最好的方案应该是 cerr 输出调试信息,写完题目后全文查找 cerr 来删除调试信息

  • 看清楚 freopen 的文件名,文件后缀名,wrstdinstdout 是否对应。你最好不要第一行写完 freopen 之后直接复制到第二行,这容易出问题!每年都有无数的人踩了这个坑!

  • 看清楚无解输出什么,不要想当然以为无解就输出 -1

  • 看清楚题目的名称,比如之前有人csp,题目名称 snake 写成了 snack ,还是建议直接从题目pdf里复制下来

  • 注意如果 #define int long longscanf 的时候不应该 %d 而应该 %lld

注意

不出现特殊情况的话我们还是非常建议使用 #define int long long,因为在需要取模的题中,你可能因为某个变量没开 long long 就导致计算结果出现错误,又或者是题目需要开 long long ,但题面并没有很明显告诉你。

还应该注意 #define int long long 会导致运行变慢,如果你确信题目 100000% 不需要开long long,而且计算出来的时间复杂度可能擦边过,那么就不要开 long long

综上,我们还是建议开 #define int long long,因为比赛的题目开 O2 且 CCF 的评测机配置不低,TLE 总是比 WA 好看些

  • 尽量使用万能头,防止忘记使用某个头文件在 windows 下编译通过但在 linux 下编译不通过(况且你可能忘记了一些函数是在哪个头文件里)。
注意

但同时,你还要注意使用了万能头会导致一些变量名冲突。比如 y1

所以你实在担心,并且想稳一稳,可以考虑比赛快结束前打开NOI Linux编译一遍

  • 多测一定要记得清空!!!!!!!!!!!!

  • 多测的时候,如果数据读到一半就可以算出最后答案了(一些特殊情况),那么你仍然要继续读入剩余数据

  • 最好不要用 ios::sync_with_stdio(false) 这种关闭流同步,这会导致包括不能与 scanf/printf/getchar 混用,和文件读写使用会出现问题等各种奇怪的问题。使用快读+cout是一种稳妥的方法。

  • cout 的时候千万不要写 endl !!!!!!!!!!,一定要使用 \n

  • 看清楚行和列。在后续写代码的时候行和列也不要搞反!!!!!

  • cout<<1.00 会输出1而不是1.00

  • 浮点数要用printf输出,如果使用cout输出的话,可能输出的内容变成了科学计数法,导致wa。

数组/变量

  • 不要开 1e6 或者是 2e5 个deque,如果能手写的话尽可能手写,不然会导致 mle 的情况。这种情况在 NOIP2022 和 NOI2022 中都已经坑害了不少选手

  • 不要对大数组进行类似于 int foo[10000000]={1} 的操作,这会导致编译时间非常慢,且编译后文件非常大,在评测时直接 ce。

  • 和上面的类似,结构体赋初始值需要在构造函数里面赋,否则也会导致上述错误https://www.luogu.com.cn/discuss/713864

  • 无向图记得开两倍数组!

  • 看清楚你要开多大的数组!

  • 如果你要打部分分,更要看好你要开多大的数组,不要想当然地直接开与部分分数据范围一样大小的数组,也不要无脑开100%数据范围大小的数组!

  • 不要就开整整好的数组大小,永远都多开几个,比如1e6开1000006的大小,这样会带来很多福利

  • 如果你的函数参数传递了数组,比如 void fun(int arr[]) ,此时在fun函数内进行 memset(arr,0,sizeof arr) 是错误的。因为arr实际上传来的是指针,而对一个指针进行sizeof操作不是其原本代表的数组的大小。因此,对于第三个参数,你需要传递确定的大小

  • 如果你代码数组有的从下标0开始有的从1开始,那你脑子必须要时刻清楚应该怎么做

  • 函数内开的变量或者是数组初始值为随机数,记得一定要赋初始值

  • 注意函数内的变量名不要和函数外的变量名相同,不然会导致各种奇怪错误的发生。比如全局有l和r,枚举四层循环时枚举了i,j,k,l

  • 无符号的数据类型和有符号的数据类型混用(运算/比较)的时候容易出现问题,多加小心!

  • 注意合适的数组清空方式!一言不合就memset容易tle

  • 如果你会指针,不要随便炫技,指针会导致运行速度变慢

  • 你最好使用 cout<<sizeof(arr)/1024/1024; 看数组占用大小来防止mle

  • 赋无穷大的时候使用0x3f3f3f3f(4个3f)可以使得两个无穷大相加不会爆int,对于long long需要使用0x3f3f3f3f3f3f3f3f(8个3f)。如果你不需要两个无穷大相加,那么可以直接使用 1e181e18 作为无穷大(大概 2602^{60})。为了防止出现re,你最好(long long)1e18

  • double数组memset初始化正无穷第二个参数用127,经测试floyed不会爆负数,且比1e10还要大

  • string类型的数组有概率会出现奇怪错误

  • string容易出问题,还是用 char[] 吧。你可以采用形如 cin>>str+1 (其中str为char数组)的方法读入字符串,而且这样读入是从数组下标为1的位置开始读入的。使用 strlen(str+1) 可获得字符串长度

  • 结构体中使用了string,再memset(a,0,sizeof(a)),在程序运行结束之后会产生奇怪的re。在linux上没有,在windows下会有。建议在结构体中字符串用char[]

  • 数组越界,可能不re,而是wa,并且可能出现灵异错误(比如把另外一个变量的值给修改了),如果你debug的时候发现灵异错误,不要以为是什么bug,可能是你数组越界了

  • 使用科学计数法,即“1e多少”这样的形式时需要注意,这种字面量的数据类型是浮点数,不是整数,即使你表示的数字没有小数点!这可能会导致ce

编译

  • 由于windows的栈空间有限制,评测的时候栈空间和内存限制是相等的,因此你可能在devc++里re了,但实际上是ac的。你需要在编译选项里加上 -Wl,--stack=102400000 开增大栈大小

  • 最好加上 -O2 的编译参数,防止你没开O2是对的,但是最后评测开O2了就re了

注意

但是你还需要注意,手动开了O2可能会导致你在debug的时候发现一些中间变量被优化掉了,看不到了,你需要心里有数

  • 使用编译参数 -Wall -Wextra -Wshadow 可以尽可能显示编译的Warning,你的程序不应该有任何Warning,除非你确定出现的Warning会发生什么

  • 你最好不要直接在代码里写 1e9 这种表达式,容易出现CE

  • 检查非void返回类型的函数是不是都有返回值,定义了返回值类型却没有返回一个值会导致RE

  • stringstream的clear不是清空! 清空要这样 ss.str("");

  • 如果比赛最后评测使用c++17,那么你要倒吸一口凉气了,因为c++17引入了若干保留字,包括但不限于 sizenext

做题

  • 如果最后结果是相减再输出,且要求取模,一定要 (a-b+mod)%mod,否则会输出复数

  • 暴力能优化就多优化优化,多过一个点可能就一等了

  • 使用滚动数组进行优化时,看好了最终答案用哪个数组!

  • 状压dp由于n2nn*2^n的极限复杂度,在枚举状态的大循环里面要避免大常数操作。比如可以预处理的数据要预处理。以及不 #define int long long

  • 一定要再三确定快读没有打错,不然全爆零!!!!

  • 要注意留出时间认真审题(最后草草审题写暴力大概率爆零)。

  • 如果题目数据比较容易生成,暴力容易写,而且时间充裕,那么最好使用对拍。

  • 对拍可以考虑生成数据范围小且比较特殊的数据,这样比中等规模随机数据相比对拍速度更快,且出现问题调试起来也更加方便

  • 两片雪花之间,注意顺序,不一定是l小于r

  • 求两点距离的dis函数,需要这样写 (double)(sqrt((double)(dx[a]-dx[b])*(dx[a]-dx[b])+(double)(dy[a]-dy[b])*(dy[a]-dy[b]))) 多加几个double,否则会造成精度损失,进而wa

  • 二分时,如果l和r有负数,那么应该用(l+r)>>1而不是(l+r)/2,否则会出现MLE错误

  • 有可能图不连通

  • 题目如果没有特殊说明,重边和自环最好也要考虑考虑

  • 判断元素是否存在于map中,不要使用 []==,要用find,因为调用[]会在map中生成一个节点 多次判断存在会使长度非常大

  • 搜索时慎用map判重!会带来负优化

  • set 要用自带的二分查找函数,否则复杂度会和暴力同等(CSPS2021T1)

  • 离散化,最后是 -a-1。二分查找最后是 -a

应试

  • 进考场之后千万不要碰电脑

  • 考试的时候不要小声嘟囔,即使忍不住嘟囔也不要带脏字,不然被判定为辱骂CCF会被禁赛一年

  • 不要拿记事本看大样例,因为大样例的换行符是linux格式的换行符,在windows的记事本中无法显示。正确的打开方式是把样例文件直接拖入devc++的窗口里查看

  • 测大样例时,devc++会显示运行时间,根据经验,你在考试的电脑上运行6s的程序都可以在正式评测时以1s以内的时间运行完毕,所以不需要担心tle问题

  • 使用fc进行对比时可能出现问题,详见本文“checker”一节

  • 由于比赛时间紧张,如果你的代码不需要重新编译的话,可以按F10直接运行,省掉重新编译的时间。

  • 不要在代码里出现CCF字样,否则禁赛1年

  • 如果需要对暴力算法进行优化,请务必确保你最开始想到的暴力算法是正确的,不然你会浪费大量时间对一个假的做法进行优化

  • 根据经验,时限比较小的题目一般是难度比较低的。数据范围给的比较简单的题目一般是难度比较低的。t1和t2中有一个本场比赛最简单题目,t2和t3有一个本场比赛次简单题目,t4永远是最难的(上述规律仅为一般规律)

其他

  • 注意运算符的优先级。如果你怕出事的话,我们建议你多用几个括号括起来

  • min(a,b)写成了(a,b)

  • for循环的上界不要随时计算,比如 i<=sqrt(q),i<=strlen(s)

补充说明

这里有个神奇的错误:string的.size()容易爆,应该先 int len=s.size() 预先储存长度大小,不然频繁调用会发生莫名错误

  • 三目判断表达式,冒号两边必须是同一数据类型。比如 (ans==10? 'X':ans) 这个代码就会出现莫名其妙的问题 (P1055)
  • 最好不要对string进行判等!
  • 数论题一定要注意取模,一个地方忘记了就会wa

常见算法错误

快速幂

  • 在引用cmath头文件,或者万能头之后,快速幂函数就不要命名为pow了。直接使用pow会调用cmath的pow,而不是你自己的快速幂函数。

并查集

  • while(x!=fa[x]) 写成了 if(...)

  • 并查集没有初始化

  • 并查集合并时注意一定是合并两个元素的祖先!!!!!

st表/倍增求lca

  • st表取lg的时候从2开始循环,倍增求lca取lg的时候从1开始循环

  • st表 j<=n-(1<<i)+1; st[r-(1<<k)+1][k] 两处注意+1

  • 倍增求lca x=fa[x][lg[dep[x]-dep[y]]-1]; int k=lg[dep[x]]-1;k>=0;k-- 注意两处-1

主席树

  • 注意开32倍大小

fhq-treap

  • split完忘了 update(now)

  • 注意开数组时除了算上操作次数,还要算上一开始就插入的数

线段树

  • 注意开4倍空间

  • 别忘记pushdown和pushup

  • 别忘了一开始build建树

  • modify操作到最后别忘返回(写dfs的时候也要注意这一点)

图论

  • 遍历图的时候,把 edge[i] 写成了 edge[x]

  • 无向图,edge数组忘开两倍

  • tarjan算法时,将点弹出栈后忘记 vis[x]=false

  • 求次短路的时候优先队列中以dis1为关键字排序

  • 求最短路的时候从队列/优先队列中取出元素后忘记pop

  • kruskal重构树开二倍大小

  • 看清楚题目让点差分还是边差分,写的时候不要把边差分和点差分写混了

checker

春季测试时给出的大样例,如果在windows系统下直接使用fc进行对比,会报有差异,即使你的输出的答案文件是一样的。这可能是大样例的换行符出了点问题。如果你遇到了相同的问题,不要惊慌,你可以手写checker来进行对比

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<bits/stdc++.h> using namespace std; signed main(){ ifstream a,b;a.open("tree.out");b.open("tree6.ans"); int x,cnt=0; while(b>>x){//直到读不到数字了才结束 int y;a>>y; cnt++; if(x!=y){ cout<<"WA at "<<cnt<<endl; return 0; } } cout<<"AC! "<<cnt; return 0; }

编译参数

你需要包含题面第一页所写的所有编译参数,除此之外,还有下面这些 -std=c++14 -O2 -Wall -Wextra -Wshadow -Wl,--stack=102400000

最后更新于:2025-01-24 06:52:27

Caiwen
本文作者
一只蒟蒻,爱好编程和算法