做完题后一定要检查数组开的大小,最好开大2~4倍。对于N,NN,NNN等多种可能大小,一定要注意!!!
2020.3.3 分清N和NN啊!!!!!怎么就是改不掉啊!!!!!!!
2020.3.19 +1
-
☆☆☆ 一定要读懂题啊!!!+1+1+1
1.测试数据长什么样都可能。
如果没说没有重边自环,一定要自己判断!!!
2.题目给的是什么、问的是什么一定要搞清楚!写read/printf时问一下:x是编号还是颜色还是i号的权值?
-
☆☆☆ 注意是否是多组数据,以及文件名是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
- 考试的时候要手造样例,并且 要依据题意!! 因为自己推出来的结论可能是错的!!
具体易错点:
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上由于格式限制比较严格(比如行末不能有多余空行,文件末尾空行也参与比较),可能会容易出格式错误。
调试相关
如果调试的时候发现某变量的值莫名发生改变(尤其是大数据时),试试开大数组,如果开大数组就恢复正常,说明哪里的数组开得不够大。
线段树相关:
(1) pushdown时是
val[ls[cur]]+=lazy[cur]*(r[ls[cur]]-l[ls[cur]]+1);
而不是
val[ls[cur]]+=lazy[ls[cur]]*(r[ls[cur]]-l[ls[cur]]+1);
并且记得把lazy传给儿子。
(2) build时是val[cur] = h[L];
而不是val[cur] = h[cur];
2019.11.14: +1
(3) 涉及laz标记时,modify要加laz!
(4)线段树一定要开4倍啊啊啊啊啊啊
2019.11.11 : +1
(5)动态开点线段树的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!!!!!!!
-
开数组一定要开4~5倍后再多开4~5(if possible)!!!否则会出奇怪的锅(其他变量莫名改变,数组莫名越界)或者用来减轻可能出的锅(如NN打成N)。
updated on 2019.11.28 分清N和NN!犯了三遍了!
updated on 2020.1.17 连双向边时数组要开大2倍!
-
一定要分清局部变量和全局变量!!
-
(dfs)一定要还原现场啊啊啊啊!!!
-
修改错误时记得要修改完!!!!
-
注意输出格式!!!!!
写并查集时要注意:
(1)find函数是:
return fa[cur] == cur ? cur : fa[cur] = find(fa[cur]);
而不是:
return fa[cur] == cur ? cur : fa[cur] = find(cur);
也不是:
return fa[cur] == cur ? cur : find(cur);
(2020.4.11:+1)
(2)合并并查集时是:
fa[find(aa)] = find(bb);
而不是:
fa[aa] = findd(bb);
(首先,复杂度会增加;其次,会出玄学错误)
取模&哈希 相关
Hash时模数(M)和进制(base)一定要用素数。
模数一般用:
1e9+7,1e9+9,19260817,998244353, 1e7+9, 1e7+7,5e5+9,
(双哈希)1520401 and 1520473, (双哈希)10007and10009,
(NTT爆int)3221225473(G = 5)
进制数一般用:
131,13331
DP相关:
DP时要注意更新顺序,有时需要倒着更新。即别人说的:
“在考虑如何递推时,通常考虑如下几个方面:
是否能覆盖全部状态?
求解后面状态时是否保证前面状态已经确定?
是否修改了已经确定的状态?
也就是说,在考虑递推顺序时,务必参考动态规划的适应对象多具有的性质,具体参考《算法导论》相关或百度百科或wiki。”
树链剖分相关
- 树链剖分来逼近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
-
树链剖分的
dfs_s(int cur, int faa)
中要先siz[cur] = 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();
- 请仔细研究SPFA中vis数组的使用情况。
while(front<rear){
cur=que[++front];
vis[cur]=false;
for(register int i=head[cur];i;i=b[i].nxt){
tmp=b[i].to;
if(p[tmp].dis>p[cur].dis+b[i].val){
p[tmp].dis=p[cur].dis+b[i].val;
if(vis[tmp]) continue;
que[++rear]=tmp;
vis[tmp]=true;
}
}
}
- 不要觉得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;
}
- 常用二分写法中是:
while (l <= r)
而不是:
while (l < r)
- 该用STL的时候就用STL,有时候人家封装的东西我是不好写出来的。
如:(set), (map), priority_queue
-
long long 是%lld,但double 是%lf,千万要注意,否则windows没事但linux会CE
-
double在强制转换成int时会自动忽略小数位,但在printf的%.3lf时会四舍五入。
-
Trie树不要写trie[26][N],要写trie[N][26],否则时空复杂度翻倍,原因玄学。(感觉好像是trie[N][26]中只频繁访问trie[0~(N/2)][0~25]中的数)
-
dinic的弧优化记得加,记得初始化!!别忘了s、t的初始化!!
-
质因数分解求单个数的欧拉函数以及欧拉筛求欧拉函数时注意:φ(ab) = φ(a) * φ(b)只适用于gcd(a, b) = 1(毕竟是数论积性函数嘛),要这么写:
//质因数分解求φ
for (register int i = 2; i * i <= m; ++i) {
if (m % i == 0) {
phi *= i - 1;//attention!
m /= i;
while (m % i == 0) {
m /= i;
phi *= i;//attention!
}
}
}
//欧拉筛求φ
for (register int i = 2; i <= n; ++i) {
if (!depri[i]) {
pri[++cnt] = i;
phi[i] = i - 1;//attention!
}
for (register int j = 1; j <= cnt && i * pri[j] <= n; ++j) {
depri[i * pri[j]] = true;
phi[i * pri[j]] = i % pri[j] ? phi[i] * phi[pri[j]] : phi[i] * pri[j];//attention!
if(i % pri[j] == 0) break;
}
}
-
对于特判情况,一定要记得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()存着容器开头的元素,可以访问!!就是所谓先闭后开!!
-
分块维护链表时,一定要分清节点编号和节点的值!!这部分非常容易出错!!
-
1e6 是 1000000,开成2001000!!不是201000!!!
-
dp一定要考虑全啊,尤其是当“强制选...”时,考虑一下会不会漏掉些情况。如果会,要想法加上!
-
字符串的题只要遇到“子串”等字眼,一定要往 SA 和 SAM 处想!!!不要再想 KMP 之类的了!如果可以,尽量把题目转化成经典模型,否则就要看灵活运用能力了。
-
注意 (cur) 和 (to) 和 (fa) 的区别啊!!!
-
搞dfn的时候要分清cur和d[cur]!!!!!
-
记得排序!!!!!!!! 如果存在两个关键字,且第一关键字可能不同的情况,要双关键字排序!!!!!尤其是求凸包以及斜率优化的时候!!! 第二关键字怎么排序都行,但就是要求有序!!!
-
调试的时候系统栈不够用要手动开栈:
-Wl,--stack=67108864
其中 (67108864 = 64 * 1024 * 1024(64MB))
-
解决网格上“互不侵犯”问题,不要光想状压,还要想想dp,想想网络流(二分图)!
-
动态开点线段树一定要分清 (ls) 和 (rs) 与 (L,mid,mid+1,R) 的对应关系,不要弄反了!!
-
树的边数是n-1!!
-
(while() ot = if()) !!!! +1
-
构建倍增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) !!!
-
递推阶乘逆元时要
%P
!! 不知道为什么老是在这里犯错。
2020.4.7:推阶乘逆元以及用阶乘逆元推较小数逆元时一定要在草稿纸上推式子!!太容易出错了!!(还是我太菜了)
-
对于区间添数,单点查第k大等等看起来神似前缀和的集合问题,要考虑可持久化权值线段树!!!不要执着于线段树分治套权值线段树!!否则复杂度多个 (log),常数也疯狂增长!!
-
(sort) 的比较函数不要命名为 (bcmp),dev没事,但是有些 OJ 会出错。建议将开头字母大写,如 (Bcmp)。
-
有时候一些数学的东西可以用dp来搞出来,比如错排数等等。因此如果感觉数学方面的某些题(尤其是计数题)光用数学的东西(如二项式反演等等)做不出来的话,可以考虑dp。
-
4000-1=3999,不是3009!!!这种傻错能不能不要再犯了?!
-
质因数分解(根号)求质因数/phi/miu/...的时候,一定记得对剩下的 (x) 不为1的情况进行特判!!
-
图论连边的时候,是
e[++ecnt] = (edge){head[from], to}
不是e[++ecnt] = (edge){from, to}
!!! -
如果题目有重边且须去重,而 (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 ...。
-
除法分块记得
l = r + 1
!!! -
前缀和要
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])!!
算法模块
斜率优化
斜率优化的第二个While(取答案)时,是(stk[] - stk[]) * xl
而不是(stk[] - stk[]) * (stk[] + xl)
!!!
FFT与NTT
-
记得取模!
-
左移和右移一定分清!!
-
关于i = 0还是i = 1:
FFT和NTT里都是i = 0,别写成i = 1。
- 关于<= limi还是< limi:
写<= limi总不会错的。
实际上我们是在对 ([1,limi)) 搞一些操作,因此都 <limi 应该也问题不大。
统计答案的时候不要写<= limi!!!
第一层循环也不要写 <= limi,写 < limi
-
到了后面(多项式乘法时)n和m的出现次数就少了,主要是limi。
-
cosnt int Gi = (M + 1) / G;以后就这么写吧,省着把332748118 写成 322748118
-
NTT和FFT的第三层循环中的p应写成(int p = 0; p < i; ++p, t = t × T % P)。
-
记住,是ax = a[j + p], ay = t × a[i + j + p]!!!别忘了乘t!!
-
NTT和FFT的第一层循环应写成(int i = 1; i < limi; i <<= 1)。
-
FFT中T为Complex(cos(PI / i), sin(PI / i) * type),横坐标是cos,纵坐标是sin!!
11. 一开始蝴蝶变换的时候是swap(a[i], a[r[i]]),不是swap(i, r[i])!
快速沃尔什变换(FWT)
- FWT不用倍长,不用蝴蝶变换
计算几何
-
在求凸包和旋转卡壳的while中,注意是<=而不是<。
-
注意叉乘乘出来的是有向面积!
-
(a, b, x, y) 之类的注意一下,不要打错字母了!
凸包
首先先不让vis[1]=true;最后的sta数组sta[1] = sta[n]。
记得双关键字排序!!!!! 第二关键字怎么排序都行,但就是要求有序!!!
旋转卡壳
-
不要让枚举的对踵点超出范围,要搞一个循环。
-
当“凸包”只有两个点时,这个算法会陷入死循环。所以要实现判一下有没有凸包。
-
求出凸包后,v[1] = v[top] = 最左下点
半平面交
-
要先排除队尾,后排除队首,不然据说会出错。
-
判断交点记得是
t1 = AC * AB, t2 = AB * AD;
,就这样写,不然会奇怪地出错。我也不知道为啥,可能这种写法对处理直线不仅只在第一象限,或者直线所用线段不交等情况更有优势吧。 -
此时(cnt,tot,top,front, rear)等变量名较多,注意区分。
Splay
-
rotate时应该先pushup(faa)再pushup(cur)(因为此时faa已经在底下了)
-
insert函数修改完后Splay前要
pushup(cur);
!
分块
-
分块如果调挂了的话,可以对拍造组小数据,玩一下数据。因为分块相对于树形数据结构(如平衡树)来说很好调。
-
分块入过的坑:
-
对小范围的特判的
continue
-
计算ed[i]的取min
for (register int i = 1; i <= n / block + 1; ++i) {
st[i] = (i - 1) * block + 1;
ed[i] = min(st[i] + block - 1, n);//attention!
}
-
这部分的 (i) 有两种可能,一种是序列里的编号,一种是块的编号,写代码时要把 (i) 分清。
-
分块维护链表时,一定要分清节点编号和节点的值!!这部分非常容易出错!!
SA
-
注意什么时候用 (rk),什么时候用 (s)。把 (rk),(sa),(s) 分清楚,SA这块的调错就问题不大了。
-
如果有多组数据,记得把数组都清空一下。需要清空的数组有:(rk,s,sa,height,id,oldrk,bin);以及(p,w,limi)。总之都清空就问题不大了。
-
注意求 (height) 时是 (s[i + k] == s[sa[rk[i] - 1] + k]),注意不要把-1搞到rk[]里面!!注意是 (sa[rk[i]-1]) 而不是 * * * * !!!!
-
求height的最后是
height[rk[i]] = k
!! -
注意 (forforfor) 中处理 (rk) 时是 (oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]),是 + !!!!
-
利用SA求lcp,用st表时,一定要在height数组上做St表,不能转化到字符上!! 因为不是求字符间最小值,是求height数组最小值,有些性质字符不符合的!!!不要耍小聪明!!!
SAM
-
一定要加lst = np。注意,是np,不是p!!!
-
数组要开二倍!!
-
while(p && !son[p][c])
不要和while(son[p][c] == q)
弄混了,可不是while(p && son[p][c] != np)
!! -
一定要
p = fa[p]
!!!
除法分块 & BSGS & exBSGS & ...
bsgs:
-
理论上讲,枚举b从0开始,不能为m,枚举a从1开始,可以为P/m !(实际上b为m好像也可以过模板题)
-
这里需要保证y,P互质,并且y>1。因此需要对y=0和y=1的情况进行特判。并且这里求出的x为最小正整数。如果要求最小非负整数,就需要对z=1的情况进行特判。
-
建议将第二个for里面的i的上限多开大10,防止覆盖范围差一点点所带来的误判。(反正多开大一点,无解的还是无解,有解的也早就找着后退出了。)
exbsgs:
在保证正确性的前提下,要尽可能地加特判(主要是对0和1的特判)。
除法分块:
除法分块中,一定要保证i<=n,否则res = n / i出现0时,lst = n / i会RE!!
逆元预处理:
一、推阶乘逆元的时候先要求的是jie[up] 的逆元,而不是 up 的逆元!!
二、注意推阶乘逆元的上届:不能超过模数 (P),否则阶乘逆元将会推出一堆0!!(毕竟0无逆元)
点分治
-
为了防止将子树内部的路径给加上,要么运用容斥,先加后减,要么先一个子树一个子树地计算贡献,计算完一个子树后合并,然后再对子树进行dfs,求子树内部的答案,不要先dfs,再一个子树一个子树地算,很不方便!
-
在求子树的重心时,子树的大小可以暂时按 (siz[to]) 来计算,而不用dfs一遍算它真的siz。至于为什么,见:一种基于错误的寻找重心方法的点分治的复杂度分析
-
计算出子树的重心后,要传重心!!! 不要传 (to) !!!不要dfs(to)!!!!否则就白计算重心了,复杂度会退化成(n^2)!!!
-
注意 (cur) 和 (to) 的区别啊!!!
-
(find_root) 的时候,注意要初始化 (f[](mxsiz[])) 和 (siz[])!! 即:$ siz[cur] = 1; ~ mxson[cur] = 0;$
-
分清to和rt!!!
AC自动机
与SAM不同,AC自动机的根节点编号为0,这样更方便些(如build时少出些锅)
AC自动机的真实节点个数为 (ttot + 1),还有一个0节点
李超树
从根到叶子的所有线段都要算一下,包括拆成小线段节点之前的大节点,但是这些线段可能没有完全覆盖询问区间,要进行特判!!!!
res = have_segments[cur] ? min(dis[ded[max(l, L)]] * k[cur] + b[cur], dis[ded[min(r, R)]] * k[cur] + b[cur]) : inf;
主席树
主席树的 $add(modify) $ 不要if (!cur) cur=++ttot;
,而是要cur=++ttot;
,因为上一层可能错误地改了 (ls[cur], rs[cur])!!!不要和动态开点线段树弄混了!!
小Bug
都是一些容易看出来的小错,希望比赛的时候不要降智,留着这种错到最后还看不出来。
-
调试语句一定要删!调试语句一定要删!调试语句一定要删!(考试最后可以直接删掉所有注释掉的调试语句)
-
输出格式记得注意依据题目要求!不要要求“n行每行一个数”却写成“1行n个数”!
-
有初始化
init
函数时记得调用 -
多测清空
-
取模题出负数记得 +P
-
预处理阶乘及逆元记得
jie[0] = jieni[0] = 1;
-
记忆化搜索的时候记得打上
if (mp.count(...)) return mp[];
和return mp[] = res;