错误集合
如题所述,专门记录自己在各类考试、做题中出现的花式错误。
2020.08.24
-
多组数据,小范围搜索,大范围骗分的时候,没有注意小范围搜索的用时,导致 TLE 。(
2020.08.24 bracket
)
处理:不要贪心,同时严格把控小范围的时间空间开销。 -
从母串 \(S\) 里面提取子串 \(T\) ,然后本应该在 \(T\) 上进行的操作,全部搞在了 \(S\) 上面,导致 WA 。(
2020.08.24 play
) -
中途修改写法,把外部的写成的某一个步骤封装或者改写成函数。其中函数的某个参数是全局变量,但是内部相关参数没有改名字,导致 WA 。(
2020.08.24 river
)
处理:善用替换功能。
2020.08.25
-
[Ynoi2019模拟赛] Yuno loves sqrt technology II
写莫队发现自己没有排序,T 飞了。
大致可以描述为:void Init() { //对 Q 数组进行排序 } int main() { Init(); ...... //读入 Q return 0; }
处理:一定要将数据读入完之后再预处理 。
-
题目见上。
写分块的时候对边角块处理不当......void UpdateBlock( int id, int l, int r, ... ); //块内处理 void UpdateAll( int L, int R ); //全局处理 { if( 左边有边角块 ) UpdateBlock( 块编号, L, R, ... ); ...... }
事实上应该将左右边界限制在块的范围内。
2020.08.26
-
【UR #2】跳蚤公路
循环变量用上了不常用的名字,结果之后就写错名字:for( 对 i 循环 ) for( int u = 1 ; i <= n ; u ++ ) //......
处理:尽量规避奇怪的循环变量名称,同时写的时候也要带脑子。
-
[CF446C]DZY Loves Fibonacci Numbers
分块写法,清理标记的时候,没有判断有没有标记:void Normalize( int id ) { //这里缺少了是否存在标记的判断 for( int k = lef[id] ; k <= rig[id] ; k ++ ) //...... }
最开始以为分块的标记和线段树类似,现在才意识到,分块下放标记的时间是 \(O(T)\) 的,所以不判掉就会特别慢......
2020.08.27
-
模拟赛。
写 DP 数组,虽然时间复杂度不对头,但是我很自信,于是就把空间开到了最大,希望能卡过。
然后它就 MLE 了,呜呜呜~
省选的时候就吃过了数据结构 MLE 的亏,今天倒是注意了这一点。
没想到 DP 数组也能 MLE ......
处理:比赛最后检查的时候一定要算一遍空间,不要太贪心。 -
模拟赛。
最后 45 分钟 rush 一个正解。由于人很慌,而且是数据结构题目,所以小数据就拼了个暴力上去,想着是有保底的分数。
测出来我就发现,我正解写对了,但是暴力居然写错了?!
于是就挂了...... -
还是模拟赛。
以为 T3 不太难,于是硬刚它。没有想到它是很恶心的结论题目,于是我就花费很多时间,换来了 10pts 的好分数。
处理:开场时每道题先粗略地思考一下,评估难度;规划好时间,避免吊死在一棵树上。
2020.08.31
-
[HDU6826] An Easy Matrix Problem
树状数组写模板写习惯了,结果现在带上取模却忘了改板子,然后就 WA WA WA 个不停。
处理:尽量少复制板子,尽量重新手写。特别是遇到取模等特殊情况的时候,一定要多多检查自己认为正确的板子!
2020.09.01
-
错误一:多组不清空,爆零两行泪。
错误二:写圆方树却没有开两倍空间,清空的范围也不够,于是就疯狂 RE 。
处理:想好了之后再开始写代码;中途有修改的话记得看一看代码中的其他地方有无影响;检查代码。
2020.09.11
-
错误:多组不清空,爆零两行泪。
梅开二度
2020.09.19
-
模拟赛。某题有多解,要求输出 " 字典序最小的一组 " 。
由于方案构造起来并不复杂,所以......直接没有看到这个要求(甚至过了大样例),暴毙。
-
模拟赛。预处理应该按照值域为范围来清,结果只清到了点数范围,继续暴毙。
2020.09.27
-
同一变量多次重复使用,中间没有清空,直接暴毙:
int lst = N + 1; for( int i = N ; ~ i ; i -- ) { nxt[i][1] = lst; if( S[i] == '1' ) lst = i; } //这里本应该清空 lst 的 for( int i = N ; ~ i ; i -- ) { if( S[i] == '1' || S[i + 1] == '0' ) nxt[i][0] = lst; if( S[i] == '0' ) lst = i; }
处理办法:
带脑子写题就好了,编写的时候要对当前变量的作用有清晰的认识,才能减少出错概率。
2020.10.06
- [HDU6334]Problem C. Problems on a Tree
画蛇添足,本身不需要用map
的地方偏偏使用了,导致程序及其慢,map
占用了将近 \(\frac 1 3\) 的时间。
处理方法:慎用 STL ,尤其是比较卡或者没开 O2 的时候。
2020.10.12
-
最大生成树,运算符直接重载为了小于。
处理方法:注意这种小细节,不要惯性思维。
2020.10.15
-
我不记得了突然想起来,于是记一笔。这个错误已经犯过两次了。
写程序的时候,中途使用了
cmath
中的函数,但并没有加上头文件。问题是,Dev-C++
的垃圾库直接把cmath
丢到了algorithm
里面,所以 ......CE!处理方法:正式考试的时候,最好用一下其它
正规的编译器。或者在检查的时候,对于每一个函数检查它的头文件是否已引用。
2020.10.20
-
题目数据范围太鬼畜,没判断
s==0
直接暴毙。处理方法:仔细阅读范围,最好能在程序/草稿纸上把数据的细节写下来。
2020.10.27
-
使用了俩优先队列的 " 可删堆 " ,结果在
push
和erase
的时候都没有清除多余元素,于是堆中冗余元素就超多,结果 TLE 了:typedef priority_queue<int, vector<int>, greater<int> > Heap; struct RHeap { Heap q, rub; void Push( const int v ) { q.push( v )/*这里需要 Maintain 一下*/; } void Erase( const int v ) { rub.push( v )/*这里需要 Maintain 一下*/; } int Size() { Maintain(); return q.size() - rub.size(); } int Top() { Maintain(); return q.empty() ? 0 : q.top(); } RHeap() { while( ! q.empty() ) q.pop(); while( ! rub.empty() ) rub.pop(); } void Maintain() { while( ! q.empty() && ! rub.empty() && q.top() == rub.top() ) q.pop(), rub.pop(); } }
处理方法:
下次记住。
2020.11.05
- 关于欧拉路,需要使用当前弧优化,
否则会 T 傻。
2020.11.15
-
使用 Boruvka 算法的时候,一定要注意,找出的是点,对应的是连通块,并查集维护的根与连通块的编号没有任何关系,千万不要用混了!
2020.11.25
-
模拟赛中出现的 typo 。
按理说应该是很常见的问题,我居然还在犯。遍历 \(n\times m\) 的平面的时候,弄混了 \(n\) 和 \(m\) 的关系,于是写错了循环边界,成功地挂分了:
for( int i = 1 ; i <= n ; i ++ ) //这里的 n 应该是 m ...
处理方法:记清楚变量的含义,尤其是常见易混变量 \(n,m,x,y\) 之类的,如果记不住就可以取有意义的名字,或者对应列个表出来。
2020.11.26
-
这道题用单调栈维护所有位置的最值的和的时候,细节很多。尤其注意的是,每个点管辖的区间实际上是从栈内的上一个元素开始的,也就是 \((stk_{i-1},i]\) ,而不能将自己作为自己那一段的终点。请参考以下这段代码:
while( top1 && a[stk1[top1]] > a[i] ) mn -= 1ll * ( a[stk1[top1]] - a[stk1[top1 - 1]] ) * ( i - stk1[top1 - 1] - 1 ), top1 --; // 这里不能写 i-stk[top1] ,不然就会少算一些位置 mn += 1ll * ( a[i] - a[stk1[top1]] ) * ( i - stk1[top1] - 1 ) + a[i], stk1[++ top1] = i;
2021.01.15
-
发呆想了半天才发现枚举的那一天可以放在钦定的前 \(k\) 个里面。
处理方法:思路一定要全,分清问题的主从关系。
2021.02.24
-
关于整体二分的写法问题。
这里由于我们不能简单地修改要求,所以在进入右区间的时候,我们必须保证左区间的边已经被加入。
如果写法是在离开区间时删除新加入的边,且在进入右区间之前将所有的左边的边加入,那么没有问题。
但是如果写法是在叶子的位置加入边,并且之后不删除,那么就必须保证叶子可以到达(现在外层相当于是遍历线段树的过程)。因此如果:
void Divide( const int qL, const int qR, const int vL, const int vR ){ if( vL > vR || qL > qR ) return ; ...}
那么就有问题(因为很有可能询问区间为空就返回,没有到叶子节点)。
2021.03.27
-
愤怒的我。简直傻到家了想了简单的 \(O(nk^2)\) 之后,觉得这个算法:啊, " 反正 ' 最多 ' 只能过 \(k\le 100\) 的部分分,就照着空间开吧 " 。
于是给 \(k\) 开了 100 的 int 的空间,于是就爆炸了!
处理方法:
你开空间一定要读题好不好呀!!!!
2021.04.07
-
校内赛某题。
取模的模数是单独输入的,但是我并没有注意模数可以是 1 ,因此在模数为 1 时,错误的初值就导致了不正确的结果。
处理方法:长个心眼,一定注意数据的范围;同时也应该养成输出的时候取一下模的习惯。
2021.05.27
-
联测某题。
在转移的时候,本应在所有数据计算完之后再对不合法数据进行清理,结果边清理边计算,导致较小的不合法数据影响了之后的结果。
处理方法:注意操作的顺序,不止是在 DP 的转移中,写的时候就应该注意到顺序的问题。
2021.06.20
-
多校赛某题。
\(n=10^{10}\) 的时候做杜教筛,预处理范围只有 \(10^6\),导致杜教筛跑得非常慢......
处理方法:熟悉时间复杂度原理,杜教筛需要预筛到 \(n^{\frac{2}{3}}\) 才可以。
2021.06.29
-
多校赛某题。
写了两棵线段树,大小不一致,并且查后继时如果没有后继则默认为 \(siz+1\)(但实际上应该是 \(+\infty\))。这就导致了在较小的树上查后继的时候,得到了较小的范围,而在较大的树上查询时会查漏一些点。
处理方法:注意大小关系,注意某些特殊值的取值(无穷大无穷小等),尤其是大小不一致时容易翻车。
2021.07.04
-
(重点易错)多校赛某题!!!!!!!!
循环上界又双叒叕写错了!把 \(Q\) 写成 \(N\) 了!!!!
更搞笑的是,我调试的时候已经发现了这个错误,但是忘记改了处理方法:一定要注意循环上下界的问题!!!并且调试发现的问题要及时改正!!!!
2021.07.13
-
(重点易错)挑战多项式
这次在预处理逆元的时候,恰好处理到了 \(2^{18}-1\) 的范围,但是实际调用时使用到了 \(2^{18}\) 的逆元,然后就爆炸了......
处理方法:一定要注意,使用任何函数/循环/数组内存时,是否会越界或超出有效范围;此外,在对复杂度影响不大时也可以适当使用快速幂/exgcd 求逆元;
2021.07.19
-
UNR #5
实在是弱智问题......居然觉得每次询问暴力跳(而不用数据结构维护)可以做到 \(O(q\log_2n)\)......
if( opt == 1 ){if( V == 1 ) continue;for( int i = FindSet( L ) ; i <= R ; i = FindSet( i + 1 ) ) if( ! ( A[i] /= V ) ) UnionSet( i, i + 1 );}if( opt == 3 ){su = 0;for( int i = FindSet( L ) ; i <= R ; i = FindSet( i + 1 ) ) su += A[i];IO :: write( su ), putchar( '\n' );}
复杂度不要想当然!冷静一下!
-
还是UNR #5 T3暴力 40pts,最后应该需要检查每一个状态,但是我直接把最后一个状态当做答案......
明明写个对拍就能找出的错,居然没有发现,也没有想过去写对拍,反而测了中样例就不管了......
即使当场只准备写骗分,也一定要将工作做齐备,对拍、人肉调错不能少!
-
注意,这道题非常之卡常,为了加速询问,需要使用 \(O(n\log_2n)-O(1)\) 的 RMQ 求 LCA。
提醒:注意根据题目需求调整复杂度。
2021.07.20
-
后缀数组模板。
注意中间的“桶”数组大小与字符串长度相同,而非与字符集大小相同!
const int MAXN = 1e6 + 5, MAXC = 300;int buc[MAXN]; // 注意不是 MAXC
2021.07.21
-
Pollard Rho 模板 LGP4718
注意,Pollard Rho 的原理是,假如 \(n=p\times q,p<q\),那么我们随机生成一组数 \(x_1,x_2,\dots,x_k\) 之后,如果 存在 \(i,j,x_i\equiv x_j\pmod p,x_i\not\equiv x_j\pmod n\),那么 \(\gcd(x_i-x_j,n)\) 一定会生成一个 \(n\) 的一个非平凡因子。
因此,朴素做法是枚举环长,也即 \(j-i\),并取 gcd;而加上 Floyd 判环法之后则是枚举 \(i\) 并检查 \(2i,i\) 两个元素。加上了倍增,其实也就是在倍增环长,因此需要每次和路径首个元素做差,而非和前一个元素做差。
2021.07.22
-
旋转卡壳模板 LGP1452 。
注意,旋转的时候是寻找 \((i,i+1)\) 这条边的对踵点,因此假如为 \(j\),那么应该检查 \(\operatorname{dist}(i,j)\) 和 \(\operatorname{dist}(i,j+1)\)。
当然将四个点对全部检查一遍自然没什么问题
2021.08.03
-
第一次犯错:以为区间反转对应的端点可以直接算出来,结果需要用
kth()
来找;注意相对顺序会改变。第二次犯错:
kth()
的时候没有下传标记。序列平衡树很久没碰过了 qwq ......
2021.08.18
-
一定要注意,如果用 Dijkstra 跑费用流,那么每次最短路求完之后一定要修改势为当前最短路的结果。
设在当前完成了最短路之后,结点 \(u\) 的最短路标号为 \(d_u\),那么在一次增广后,原先图上的边 \((u,v,w)\) 仍然满足 \(d_v\le d_u+w\)。而如果当前边有流经过,反向边 \((v,u,-w)\) 被加入图中,那么 \((u,v,w)\) 必然在原图最短路上,也即 \(d_v=d_u+w\),反过来也可以得到 \(d_u=d_v-w\),所以此时 \(w'=w+d_v-d_u=0\),仍然是非负的。
最初的时候我们会跑一次最短路求出势,而这个势只是对于原图有效,增广后就不能再使用它了。由于原图上可能有一些特殊性质,因而我们可以用非 Bellman-Ford 类算法求出它,以获得较好的复杂度。
2021.08.19
-
「Gym102798E」So Many Possibilities...
实数概率 DP,因为 \(\epsilon\) 设得太大,导致用它判空状态的时候将有效状态也判成空,漏了不少结果,答案偏小......
用实数算 DP 的时候,没有必要用上 \(\epsilon\)。需要用来卡常的时候,算好可能的系数量级,然后反推 \(\epsilon\) 的大小;宁可设得小一点,也不要漏掉有效状态。不要再出现这样不知所谓还被卡了半天的错误!
2021.08.30
-
「Gym102979L」Lights On The Road
第一次写 K 短路,结果被坑到了两个地方:
-
建图都建错了,头尾两个虚点不能连在一起,而 \(n=1\) 的时候我会连起来。这个写的时候确实没想到;
-
当 \(n=1\) 的时候,最短路树上没有非树边,这个时候堆为空。将空节点插进去做 K 短路就会 RE......
这个问题更大一些,写的时候想到了这一点,但是没有想到堆会成为空,没有判。
处理方法:注意边界情况,不要被细节卡了。
-
2021.09.09
-
某道计数题。
数据范围明确给出:
这就说明,输入数据可以有 \(n<k\)。但是我的程序,如果不特判 \(n<k\) 就会干出奇奇怪怪的事情,导致各种 WA。
明确提示:
- 一定要注意数据范围,注意特判边界情况;
- 读题不要想当然,如果有时间可以考虑更全面的情况,把程序修改得尽量健壮;
- 造数据自测和对拍的时候一定要注意达到任何可能的边界,极大极小都要碰到;
- 检查流程不能遗漏,模拟赛练习的就是保持稳定的能力,白丢分比不会做更可怕!!!
2021.09.12
-
关于不同颜色计数的树上差分方式。
如果按照 DFN 排序之后,某个颜色之前和之后都有颜色出现了,那么应该选择一个较低的 LCA 来删除多于贡献,而不应该在两个 LCA 上都删除一遍。
-
调了一个半小时,终于发现问题所在:竟然是反向边弄错了,最初
cnt
赋值为 0 而非 1 !!!处理方法:单个变量不要忘记初始化!
2021.09.14
-
答案的范围是 \([0,2^{31})\),所以计算 \(L,R\) 的时候,如果中途不取模可能会爆
int
。处理方式:涉及到的变量如果非常之大,每一步都取模是必要的;对拍的时候也应该造完全极限的数据。
-
题目本身并不难,但是要注意,由于我们将值存储在
Trie
的末尾,所以被删除了的节点本质上就是变成了空节点。因此,空节点存储的值应该等于被删除了的值。例如,如果空节点值为0
,那么被删除的节点的值也应该是0
。否则,
Trie
的结构就可能导致将空节点判断为有值的节点的错误出现(例如,插入一个较长串,并查询它的前缀)。处理方式:同类标记尽量统一。
2021.09.16
-
注意:左偏树删除堆顶的时候,它的标记可能还没有下传。因此需要下传标记之后再合并左右子树。
有的数据结构也是同样的。如果在删除的时候,被删除的节点对于其他节点的影响需要全部清除。
2021.10.02
-
一道重链剖分的题目,写错了两个地方:
- 其一,居然把重链剖分写成了轻链剖分😢,就一个符号写错了;
- 其二,中途某个位置没有开
long long
,一直没有检查到;
处理方法:第一个问题属于写错了只会影响复杂度的类型,应该多测一下极限数据,什么样子的都应该测一下;第二个问题则应该检查每一个变量类型开得是否合理。
-
最终求出来的次小生成树的答案可以达到 \(10^{14}\),但是最大值只开到了 \(10^9\)。
处理方法:注意,不同的数据类型应该单独设计最大值,最大值不可通用!
2021.10.11
-
模拟赛,有一道题目改到一半弃疗了,结果忘了改成正确的程序,导致💥
处理方法:检查时间要留充分,需要检查文件、空间、编译和所有样例!!!
2021.10.12
-
比较下面的两种转移写法:
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ ) #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- ) int h[][]; //other codes omitted. per( p, N, 0 ) per( q, N + 1, 0 ) { int tmp = 0; for( int k = 0, up = 1 ; i * k <= p && j * k <= q ; k ++ ) Upt( tmp, Mul( h[p - i * k][q - j * k], Mul( ifac[k], up ) ) ), up = Mul( up, Add( g[i][j], k ) ); h[p][q] = tmp; }
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ ) #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- ) int h[][]; //other codes omitted. for( int k = 1, up = 1 ; i * k <= N && j * k <= N + 1 ; k ++ ) { up = Mul( up, Add( g[i][j], k - 1 ) ); for( int p = N - i * k ; ~ p ; p -- ) for( int q = N + 1 - j * k ; ~ q ; q -- ) Upt( h[p + i * k][q + j * k], Mul( Mul( ifac[k], up ), h[p][q] ) ); }
如果要求用
g[i][j]
将所有k
都转移一遍之后,h[][]
才能发生变动,那么第二种写法就有问题。这是因为,转移过程中较小的k
会对较大的k
造成影响,相当于是按照k
划分而不是按照g[i][j]
划分。总而言之,明确 DP 过程的阶段,同时找出合适的写法。
2021.10.16
-
关于 SPFA 判断有无负环的注意事项:
- SPFA 只能判断从起点出发能不能遇到负环。如果有一个负环不能从起点出发达到,这个负环就碰不到。
- 一些简单的优化有极佳的效果,例如
dist[]
初值设置为 0 和 DFS 版 SPFA 在负环判断中表现良好。
-
模拟赛:
策略出大问题,后面两道题目选一道死磕,以为自己写好了正解,结果发现读错题了,最后暴力都没写过。
总结:一者,如果模拟赛的后面两道题明显不简单,则一定要积极写部分分,千万不要死磕正解;二者,写较难的题目的时候一定要从暴力先写起,一来可以验证正确性,二来可以保底;三者,给每道题安排好时间,避免死磕在某一题目上。
2021.10.17
-
模拟赛:
以为自己 T3 过了,其实是假算法,
还过了大样例,挂麻了。总结:对于有一定思维过程的题目,只要不是太难写,一定要写好纯暴力对拍!
2021.10.18
-
模拟赛:
第一题比较简单,以为自己可以写对,并且觉得对拍效果不大,结果就没有写对拍,然后就 WA 爆了。
总结:前面两道题目一定不能丢分!!!基础题目一定要保证稳当!!!一定要留出 30min+ 的时间检查题目,尤其是前两道题!!!写对拍!!!
-
模拟赛第一题的错法:
如果在二分过程中计算方案,那么最终二分出来的答案是
l
而不是上一次代入的参数。因此,输出方案之前需要重新构造一遍!!!
2021.10.19
-
关于高斯消元。
之一:高斯-约旦消元的精度似乎比高斯消元的精度要高。
之二:跟行列式不同,需要将单行的主元系数化一。
-
关于 WQS 二分。
我们二分的是切凸包的斜率。如果目标位置被共线的边卡住了,那么我们只能二分出正确的斜率,而多半不能找到正确的切点。但是我们只需要知道截距和斜率就可以算出答案,所以此时的答案不是切点的结果,而是目标位置在该斜率下的结果。
2021.10.20
-
关于取模安全的问题
其实就是计算过程中,尤其是取模,很容易写着写着就忘记取模了。
简单的加减乘除还比较容易记住,但是像自加、自减这样的运算很容易忘记。
比较安全的做法是:
- 封装几个函数替代常用的运算,然后在函数内部取模。运算时强制使用它们;
- 封装模域类,然后只用这个类运算。这个对于多模数的情况比较友好;
-
关于网格图上高斯消元的问题。
设网格大小为 \(n\times m\),且 \(n,m\) 同阶。这类问题一般有两种解决方案:
-
带状矩阵高斯消元,本质上就是利用位置 hash 的性质偷了个懒,大大减少了所需扫描的范围。复杂度为 \(O(nm\times \min\{n,m\}^2)\)。
但是这个方法在网格图消元之外比较危险。网格图上可以保证对角线上系数为 1,但是其它问题中就不一定了。如果出现了需要换行的情况就比较麻烦,不好好处理很容易直接破坏掉带性质;一般来说,如果需要换,那么我们会选择换列而非换行,这是因为如果某一列之下有超出带的范围的系数,我们可以在之后的消元中解决掉它。
-
确定主元,这个相对来说适用性更广,效率更高,可以做到 \(O(n^3)\),但是写起来较为复杂......
-