注意!!
总结
- 做完题后一定要检查数组开的大小,最好开大2~4倍。对于N,NN,NNN等多种可能大小,一定要注意!!!
2020.3.3 分清N和NN啊!!!!!怎么就是改不掉啊!!!!!!!
2020.3.19 +1
2020.10.5 +1
- 一定要读懂题啊
1.测试数据长什么样都可能。
如果没说没有重边自环,一定要自己判断!!!
2.题目给的是什么、问的是什么一定要搞清楚!写read/printf时问一下:x是编号还是颜色还是i号的权值?给的边的信息是 \(x,y,z\) 不一定就是 \(x \to y\) 边权为 \(z\),可能是 \(y \to z\) 边权为 \(x\),甚至可能是 \(y \to x\),边权为 \(z\)!
-
注意是否是多组数据,以及文件名是uoi.cpp还是oui.cpp
-
注意别爆int,define int long long在考场上应该能用。如果爆long long 试试开unsigned long long,或者转成long double 类型计算后再转回来。
-
写的时候脑子一定要清楚一些, 千万不要犯把N写成NN,或tmp1写成tmp2,或ls写成l,或j写成i等等低级问题!!!
如果写题(尤其是调题时)脑子不清醒或有些急躁时,最好出去洗把脸转一转在回来。不然可能会越写越乱,还影响心情。
-
集训一定要挑一个好的地方(if possible),否则会出看不清黑板、看不到黑板、无法集中注意力等等一系列问题。
-
比赛时千万不要慌。
(说了也是白说) -
比赛时想题时先不要管常数优化,不要光为了优化常数,不去优化复杂度,因小失大!
-
记得初始化!!(多测不清空,考后两行泪)
2019.11.22更:若手写队列,则记得front = rear = 0;
-
如果希望备份一下代码,不要直接复制粘贴cpp文件!! 会出锅!!(似乎是电脑路径的问题)要复制粘贴代码,新建cpp文件!!
-
对于正规试题,如果不会正解,要想一下部分分做法,并尝试延伸。通常优秀的题能够从部分分中找到线索的。
-
正式考试时,面对毒瘤数学题(尤其是\(n<=1e9\)),打表 是非常重要的!有了打出来的表,我们可以不用推二项式反演卡特兰数欧拉定理扩展欧几里得等等,或许能直接出答案。(如:概率论 等。)(当然,很多时候推出来规律还要算阶乘,算组合数甚至使用fftnttfwt等等)不要小看这些奇怪的方法!!
-
网络流和dp比较难想,又通常是一些奇怪题的解法,因此毫无思路的时候或许可以尝试一下这两种方向。
-
开大数组!!!一定要检查数组大小开得够不够大,不仅是在比赛的时候!!!
-
一定要对拍!!!就算不会拍,也一定要多玩几组数据啊!!!大样例也不一定强(D1T2就算个例子)。考试最后20分钟的时候,反正也不可能写出一个正解了,就要查数组大小开得对不对,会不会MLE或RE; 查样例能不能过,会不会CE; 查相近变量是否写错(如l和L,i和j,ADD和Add); 简单再次读题,查是否有细节没有注意; 简单快速浏览一遍代码,查是否出现常见错误(如网络流弧优化,dp转移顺序,Splay的pushdown等等); 查文件名
-
一定要去linux环境下测试一下!!主要看有没有CE,以及样例是否正确!!!
比如printf("%.2llf",d)
以及typedef long long uint
之类的东西在Win环境下不会报错,而在linux环境下会CE!!!
linux 的使用: Linux
-
考试的时候要手造样例,并且 要依据题意!! 因为自己推出来的结论可能是错的!!
-
不要总想写部分分,要想想正解怎么写!正解有可能比部分分还好写
-
比赛时不要每个 subtask 都写!如果会正解,就直接写正解和最暴力的暴力即可,这样足以稳住这个题!否则浪费时间!
-
不要在一道题上花超过比赛时长的一半的时间(尤其是 T1)!贪心不会证,又找不着反例,那就这么写,有时间再拍!
-
有优化作用的剪枝一定要加,尽管可能在最劣情况下它用不上。因为没准测试点没有最劣情况,或者有点测试点情况不够劣,这样就可能多过一些点,甚至 A 掉
-
CodeForces 中写 Hash 最好要写双哈希(如 3221225473 和 Rand()),防止被大佬卡。注意要随机化,双哈希还是可以卡的,但是随机化+
srand((long long)new char)
就不能卡了。 -
手造样例造得越多,越强,越好。有时候手造样例比查看源代码更有利于检查(通常情况下是这样的),因为有些错误可能不太好发现。
-
简单题做完后注意想想特殊情况,不要再在简单题上挂分了!
-
考试的时候有什么小思路最好要试一试,就算这时候还有其它看起来更可行的思路,也要将这种思路记下来,那个思路行不通的时候回来再想想。不管时DFS还是BFS还是迭代加深,要把所有思路都试一试!(六月省选D1T2就是个例子)
-
提答题不要看完题面看完数据点的数据范围就去写暴力或随机化去了,一定要观察一下数据的特点!比如是不是链,是不是团,或者是不是都是质数,是不是 \(\varphi\) 都很小之类的。
-
对拍最好不要直接
int n = 100, m = 100
,可以多尝试一下 \(n,m\) 不同阶的情况,或者直接int n = rand() % 100 + 1, m = rand() % 100 + 1
。 -
对拍树题的时候最好多构造一些特殊数据:度数较多的类似菊花图(以较大概率连某些特定点)和深度较深的类似链(每个点向前 60 个点中的点连边)
-
交互提答测样例之前要先编译啊!
-
比赛(四个半小时)的时候最好想一个半小时就去写写代码,稳一稳,并且缓解一下压力
-
要有信仰啊!复杂度跑不满的可以开大数组啊!或者写几个骗分,或许就可以骗到一些分啊
-
考场上代码出现异常情况,还是要仔细想想其中的原因,不要随便找个理由骗自己。(比如开了
14000000
个vector
后连小样例都要跑几秒,dsu 在没有正确处理重儿子的时候跑链等构造数据会慢几倍,长链剖分没删调试语句导致跑链复杂度退化慢几倍) -
写完代码后即使过了样例,还是要通读一遍比较好,可以把一些小错揪出来
-
注意观察数据范围。如果感觉题不可做,可以再看两眼题,检查是否漏了一些条件。
-
暴力也要造大数据测试啊!说不准哪里爆
long long
之类的呢
具体易错点:
POJ上CE:
- 换掉万能头
- 检查有没有把一个long long当成int传进函数里
- 把POJ上的语言改成G++
- 如果dev都过不去,显示
invalid types 'int[int]' for array subscript
,那说明把一个int当成int[]来用了,检查一下有没有重名。
POJ上RE:
- 把POJ上的语言改成C++或多试几种语言,检查有没有把一个long long当成int传进函数里
- 直接把数组开大10倍(或能开大几倍就开大几倍),以防止N,NN分不清
- 检查是否可能会“sqrt(-xxx)”
- 检查是否可能会“xxx/0”
- 检查是否可能会“h[-5]”
- 检查是否可能会“h[1047483647]”(因为死循环或传错变量等)
- 与标程对拍或将自己的程序嵌入标程中提交
- 放弃该题或重构代码
OJ上MLE:
- 适当调整数组大小(调大或调小)
- 检查是否陷入死循环
- 优化算法
OJ或考试上WA:
- 数据小的话可以手玩数据
- 如果大数据WA的话,试试开大数组
- (尤其是莫名出负数时)
defind int long long
- uva上由于格式限制比较严格(比如行末不能有多余空行,文件末尾空行也参与比较),可能会容易出格式错误。
- Win下没问题但是Linux下CE的几种情况:
typedef long long uint;
printf("%.3llf\n", ans);
define int long long
然后typedef unsigned int uint;
-
如果调试的时候发现某变量的值莫名发生改变(尤其是大数据时),试试开大数组,如果开大数组就恢复正常,说明哪里的数组开得不够大。
-
动态开点线段树的pushdown的时候记得检查是否有左右儿子,防止标记丢失。
-
比较字符串时还是乖乖用
strcmp(a,b)
吧。
附:
char a[N],b[N];
strcmp(a,b);
//if(a>b)return 正数;
//if(a==b)return 0;
//if(a<b)return 负数;
注:必要时可以用STL string。
-
cnt,cnt1,cnt2,ct,cntt......一定要分清啊啊啊啊
-
左移和右移一定要分清!!!!
-
看清题目要求,不要盲目四舍五入/下取整
-
如果实在检查不出错,就试试重构代码吧(毕竟重构代码时思路会比第一遍更清晰)。
-
DFS时一定记得不要冗余遍历!尤其在求排列方案时不要选完第5,6种后再选第6,5种,否则就不仅仅是TLE了。
-
一定要记得检查是否可能会爆int/long long!!!不!!尽量把int改成long long!!!!!!!
-
开数组一定要多开一些!!!否则会出奇怪的锅(其他变量莫名改变,数组莫名越界)或者用来减轻可能出的锅(如NN打成N)。
updated on 2019.11.28 分清N和NN!犯了三遍了!
updated on 2020.1.17 连双向边时数组要开大2倍!
-
一定要分清局部变量和全局变量!!
-
DFS一定要还原现场啊啊啊啊!!!
-
修改错误时记得要修改完!!!!
-
注意输出格式!!!!!
-
DP相关:
DP时要注意更新顺序,有时需要倒着更新。
是否能覆盖全部状态?(正确性)
求解后面状态时是否保证前面状态已经确定?(无后效性)
是否修改了已经确定的状态?(有没有写挂)
- 树链剖分来逼近LCA时要:
if (dep[top[x]] < dep[top[y]]) swap(x, y);
不要:
if (dep[x] < dep[y]) swap(x, y);
2020.4.15: +1
2020.4.29: +1
-
get_lca(int x, int y)
里面要while(top[x] != top[y])
,而不是if(top[x] != top[y])
!!!!! -
输入相关
-
用写的快读(read)时要注意:此时已经把数后面的一个字符给输进来了,用getchar()时不要用错
-
输入单个字符的正确方式:
char ch[5];
scanf("%s", ch);
ch[0]...;
不到万不得已不要用getchar();
- 不要觉得memset 0x3f 就万事大吉了,两个memset过的数一加或一乘就爆了。
一般的解决办法是:判断若两个都是0x3f,就不让他们俩加或乘了,直接赋值0x3f。
- 树上三点间两两路径有重合问题(公共LCA)的关键点是分叉点。可以直接取dfn相差最大的两个点的LCA。题目可参考紧急集合,代码可参考:
int main() {
register int bb, cc, la, lb, lc, labc, ans;
while (q--) {
read(aa); read(bb); read(cc);
la = lca(bb, cc);
lb = lca(aa, cc);
lc = lca(aa, bb);
labc = la ^ lb ^ lc;
...
}
return 0;
}
-
double在强制转换成int时会自动忽略小数位,但在
printf
的%.3lf
时会四舍五入。 -
dinic的弧优化记得加,记得初始化!!别忘了s、t的初始化!!
-
对于特判情况,一定要记得
continue
或return
!!! -
SPFA的 \(que\) 要开nm,开不下就稍小点,但不能只开n,实在不行用STL的循环队列(当然,用双端队列更好了)。
-
树上需要用到dep数组的算法,把根节点的dep设为1最宜。因为如果恰好要倍增求一个点与根节点的LCA,那么这个点就会跳到0,然后炸掉。
-
计算几何,决策单调性优化DP的单调队列/单调栈中,如果当前和队尾一样优,一定要弹掉队尾!(否则会锅)
-
线性基插入时注意判断
(a[i] >> j) & 1
,即 \(j\) 位是否为1,不为1就不能插入当线性基。 -
STL容器(如set)的end()不存东西,访问RE,但begin()存着容器开头的元素,可以访问!!就是所谓先闭后开!!
-
分块维护链表时,一定要分清节点编号和节点的值!!这部分非常容易出错!!
-
dp一定要考虑全啊,尤其是当“强制选...”时,考虑一下会不会漏掉些情况。如果会,要容斥!
-
字符串的题只要遇到“子串”等字眼,一定要往 SA 和 SAM 或 AC 自动机处想!!!不要再想 KMP 之类的了!如果可以,尽量把题目转化成经典模型,否则就要看灵活运用能力了。
-
注意 \(cur\) 和 \(to\) 和 \(fa\) 的区别啊!!!
-
搞
dfn
的时候要分清cur
和d[cur]
! -
记得排序!!!!!!!! 如果存在两个关键字,且第一关键字可能不同的情况,要双关键字排序!!!!!尤其是求凸包以及斜率优化的时候!!! 第二关键字怎么排序都行,但就是要求有序!!!
-
调试的时候系统栈不够用要手动开栈:
-Wl,--stack=67108864(win)
ulimit -s 131072(linux)
其中 \(67108864 = 64 * 1024 * 1024(64MB),131072=128 \times 1024(128MB)\)
-
解决网格上“互不侵犯”问题,不要光想状压,还要想想dp,想想网络流(二分图)!
-
动态开点线段树一定要分清 \(ls\) 和 \(rs\) 与 \(L,mid,mid+1,R\) 的对应关系,不要弄反了!!
-
构建倍增st表时,要先枚举 \(j\) (等级),再枚举 \(i\) (节点/位置) !!!
-
st表一定要保证
f[i + (1 << (j - 1))][j - 1]
中的i + (1 << (j - 1))
不超过 \(n\) 啊!!!否则会RE或WA。并且f[][]
可能为负数是一定要格外注意,要保证f[i + (1 << (j - 1))][j - 1]
存在,即i + (j << 1) - 1 <= n
!!! -
对于区间添数,单点查第k大等等看起来神似前缀和的集合问题,要考虑可持久化权值线段树!!!不要执着于线段树分治套权值线段树!!否则复杂度多个 \(log\),常数也疯狂增长!!
-
\(sort\) 的比较函数不要命名为 \(bcmp\),dev没事,但是有些 OJ 会出错。建议将开头字母大写,如 \(Bcmp\)。
-
有时候一些数学的东西可以用dp来搞出来,比如错排数等等。因此如果感觉数学方面的某些题(尤其是计数题)光用数学的东西(如二项式反演等等)做不出来的话,可以考虑dp。
-
4000-1=3999,不是3009!!!这种傻错能不能不要再犯了?!
-
质因数分解时,一定记得对剩下的 \(x\) 不为1的情况进行特判!!
-
如果题目有重边且须去重,而 \(n,m<=1e5\),那么可以尝试使用 \(vector\) 存边,然后
for(i)sort(v[i][begin->end]),unique(v[i][begin->end]);
-
\(tarjan\) 求强连通分量的时候注意入栈时打上 \(instk\),出栈时记得消掉!!
-
如果需要设置一个“无穷大” \(inf\),不要卡着值域设!! 否则有些“无穷大”的性质就不存在了。(如 \(inf + k = inf\),\(inf-k=inf\) 之类的)
-
慎用typedef,除了
typedef long long ll,typedef unsigned long long ull, typedef unsigned int uint
以外最好不要用typedef,linux会CE。 -
如果有负数,不要用unsigned ...。
-
前缀和要
sum[i] = sum[i - 1] + f(i)
,不是sum[i] = sum[i] + f(i)
。 -
有些式子要放在循环里面,有些要放在循环外面。
-
写代码的时候
<=
还是<
要想清楚。 -
写 st 表的时候,注意第二块是 j + (1 << (i - 1)) 而不是 j + (1 << (i - 1)) - 1!
-
数据结构套数据结构,树套数据结构之类的东西一定要分清 \(x\) 和 \(rt[x]\)!!
-
01背包记录转移路径的时候要判
如果(取这个物品 前 不合法 && 取这个物品 后 合法) path = 这种转移,f[][]=true
,即能不用这个物品来转移,就不用这个物品来转移。要不然可能会出现同一个物品被选多次的情况。(当然前提是保证循环的顺序是合法的) -
bool 类型的DP不一定都要用 bitset 优化,有的时候可以通过可行性转最优性来获得更强有力的优化。
-
被卡空间的时候可以考虑现场求一些耗时不大的东西而不是预处理。
-
dijkstra的时候记得
q.pop()
-
想要
puts
一个字符数组,必须保证想要输出的那部分内容外其余部分的 ASCII 都为 \(0\),即先清空再填数组再puts
。 -
写Splay的时候被卡复杂度,可以在所有改变树形态的地方和觉得递归较深或与复杂度深度有关的地方,都加个
splay
-
exgcd求 \(ax+by=c\) 的特解以及 \(a\) 的最小非负整数解的时候要保证 \(a,b\) 均为正数!如果不为正数,取模会出一些问题!要先转化为正数,最后再转化为负数!
-
CDQ分治解决三维偏序问题的时候,一开始要按三关键字排序!不管是啥(计算几何等)最好要按照多关键字排序!
-
unsigned long long
值域里的随机数最好用这种方法(尤其是在 Windows 环境下):
inline ull Rand() {
ull v = rand();
v = v << 16 ^ rand();
v = v << 16 ^ rand();
v = v << 16 ^ rand();
return v;
}
如果直接用乘法(rand() * rand() * rand() * rand()
)的话,有很大概率会随机出 \(0\)(Win期望随机出 \(\frac{4}{65536}\) 个 \(0\);linux期望随机出 \(\frac{4}{4294967296}\) 个 \(0\))
-
线段树标记永久化记得不仅要记个
sum
表示子树内的标记总和,还要记个val
表示当前节点的标记的和 -
单调栈的那个“后继”的意思是它后面的第一个大于/小于它的数,和
lower_bound
一点关系也没有啊!!那个后继的意思是大于等于它的最小数啊!! -
权值线段树,权值树状数组,求前 \(k\) 大的和的时候,要注意某个值可能有多个数出现的情况啊!那个时候应该加上 \(k \times L\),而不是直接
return
! -
CDQ 偷懒用sort的时候,cmp注意一定要严格按顺序排序!例如对第二维排序的时候第一关键字为第二维,第二关键字为第三维,第三关键字为第一维,不然会挂掉!
-
写 LCT 的时候,对 Splay 中的一个点的操作(如查询)要 splay 到根,对一条链中的一个点的操作要 Access,否则信息会出错!
-
写主席树的时候能不能用点心啊!不要忘记新建节点,不要忘记继承信息,不要
if(pos <= mid) query(ls); return query(rs)
-
回滚莫队要注意回退
nwl
,恢复信息! -
可持久化数据结构要继承所有信息,包括儿子。不然最后会成为一堆链。
-
一般的网络流建图反向边的容量为 0!
-
边 \((x,fa)\) 的 \(fa\) 方向的子树不是扣掉 \(fa\),而是扣掉 \(x\)!
-
区间 DP 记得考虑一下两个区间并列的情况,不要只考虑到“缩左右点”的情况
-
写倍增比较深度的时候注意是
dep[f[j][x]] >= dep[anc]
,不要比较编号 -
注意置换的 \(p_i\) 表示第 \(i\) 个数是原来的第几个数。不要和逆置换(第 \(i\) 个数跑到了哪里)弄混了
-
树哈希就用 \(f_x = 1 + \sum_y f_y prime(size_y)\),其他会错!注意 \(prime\) 预处理够!
-
树有两个重心并不代表着这两棵子树同构啊
-
实在不会做写大爆搜的时候,可以考虑记搜以优化复杂度
(以下为退役以后添加的)
-
GCD 题如果不是一眼能看出来怎么简化的题,可以手玩辗转相除 题。尤其是如果求 GCD 的数很多的话注意数的差分!题 注意越求 GCD 越小(最多变小 log 次)!
-
树形 DP 中形如“消防局”题(距离 \(x\) 不超过 \(d\) 的点都选则多少多少收益),如果记状态为已经保证向下 \(i\) 层都选,需要保证距离 \(x\) 至少 \(j\) 层都要选,那么一定要注意 \(j\) 不只是限制 \(x\) 向上的部分,也限制 \(x\) 向下的部分(\(i\))!题
-
ACM 如果是三人单机的话尽量不要对拍占机时!尽量用瞪眼法 + 手造样例,对拍能拍出来的可能性太小
-
卡题卡了很长时间通常是一些特殊情况搞的鬼,比如上题中的影响兄弟的问题(当然这是个大问题),比如三维旋转卡壳的非共面线段问题,比如各种关于0的特判(题),比如猜的结论有问题(如CCPC网络赛2023中完全图删边最短路计数问题)......要换个角度思考哪里出问题啊!警惕有些恶心出题人构造各种恶心的特殊数据
-
关于0的特判!!(如高精度乘法关于0的特判!)
-
01-BFS要以 SPFA 的写法写,不能打 vis!考虑这样一个01边权的有向图:1-2(0),1-3(0),2-4(1),3-4(0),这里会把 4 更新两次!也即01-BFS每个点可能(且应该)会被更新两次。如果写法是普通 BFS(打vis),可能会拒绝更新第二次而出错。解决方法是允许二次更新,这里我们一定会先拿出来一个点的两次更新中距离更近的那个,所以不用担心距离更远的那个有任何非法转移。
-
求树上最短路径包含 \(u,v(u \not= v)\) 两点的点对数量的时候,要分两种情况:一种是 \(lca(u,v)\) 不在 \(u,v\) 之中,则数量为 \(siz(u) * siz(v)\);另一种是(例如) \(lca(u,v)=u\),这时候数量是 \((n - siz(lst(u, v))) * siz(v)\),其中 \(lst(u,v)\) 表示从 \(v\) 到 \(u\) 的路径上最后一个非 \(u\) 的点是什么。这个可以用树链剖分快速求:
inline int get_lst(int u, int v) {//u is v's ancient
int lst = -1;
while (top[v] != top[u]) lst = top[v], v = fa[top[v]];//Attention: `lst = top[v]`, not `lst = v`!
return v == u ? lst : dedfn[dfn[u] + 1];
}
小Bug
都是一些容易看出来的小错,希望比赛的时候不要降智,留着这种错到最后还看不出来。
-
调试语句一定要删!调试语句一定要删!调试语句一定要删!(考试最后可以直接删掉所有注释掉的调试语句)
-
输出格式记得注意依据题目要求!不要要求“n行每行一个数”却写成“1行n个数”!
-
有初始化
init
函数时记得调用 -
多测清空,同一个测试数据内调用多次的东西(比如
head
)也要清空! -
取模题出负数记得 +P。建议取模题最后输出答案都用
printf("%d\n", (ans % P + P) % P);
-
预处理阶乘及逆元记得
jie[0] = jieni[0] = 1;
-
记忆化搜索的时候记得打上
if (mp.count(...)) return mp[];
和return mp[] = res;
-
特判一定记得
continue
或者else{...}
-
基数排序要注意区分
nw
和id[nw]
!! -
特判不要忘记
return/continue
,并且return/continue
的时候一定要注意检查是否有东西没算或没清零! -
注意区分一个数是正数还是负数!不要减去一个负数还觉得自己是减去了一个正数!
-
以后快读都加上负数判断!不加就容易忘,并且加不加对速度影响还不大
-
离散化后注意到底应该用原值还是离散化后的值。
-
void ADD(int &a, int b)
要写&
,但是int Add(int a, int b)
不要写&
. -
离散化
htot=unique(h+1,h+1+n)-h-1; lower_bound(h + 1, h + 1 + htot, a[i]) - h
,注意lower_bound
中是 htot,不是n
!并且离散化以后也要注意用n
还是用htot
!(错过不下三遍了) -
DP 转移的时候不要忘记乘上 dp 值啊!不要只乘个系数就跑了!
-
要注意区分下标和值啊!尤其是排列的数组和位置数组,以及
dfn
和dedfn
-
空 vector 也占用不少空间!大概一个空 vector 顶三四个(或者五六个) int。最好不要开超过 5000000 个vector!检查运行空间的方法见我的博客,空 vector 因为需要初始化所以可以被检测出来。
-
vector 的 clear 并不会立即释放空间,等到析构的时候才会释放空间。立即释放内存的一种方法是
vector<int> ().swap(vec)
,创建一个马上就会释放内存的空 vector 并交换内存。 -
取模的时候要注意
%
产生的负数啊!尤其是判断两数相等的时候以及使用ADD
的时候,还有用模数做数组下标(哈希表)