常见小错误 FREQUENTLY MADE MISTAKES IN OI
重点检查对象
- 检查板子,仔细核对易错点
- 检查清空,哨兵,0 号元素,初值设置等等
- 数组大小
- 溢出、类型转换、inf 大小
- corner: 有重复元素、0、\(n=1\),等等,注意阅读数据范围
- 数组下标
- 注意判空、判边界,防止越界
- 检查流程控制:if, continue, break, ...
- \(n, m\) 写反
...
n. 对拍,拍一些小数据,一些特殊 case
拍子
Linux 下
#include<bits/stdc++.h>
using namespace std;
int main(){
int r=0;
while(true){
printf("%d: ",++r);
system("./gen > data.in");
system("./a.out < data.in > my.out");
system("./bf < data.in > ans.out");
if(system("diff my.out ans.out")) return 0;
}
return 0;
}
Windows 下
#include<bits/stdc++.h>
using namespace std;
int main(){
int r=0;
while(true){
printf("%d: ",++r);
system("gen.exe > data.in");
system("a.exe < data.in > my.out");
system("bf.exe < data.in > ans.out");
if(system("fc my.out ans.out")) return 0;
}
return 0;
}
代码习惯
- 时刻提醒自己要清空
- 注意溢出等问题(尤其是 unsigned 类型下溢)
- 注意类型转换,如
a.size() - 1应改为(int)a.size() - 1,long long 和 double 比较会转成 double 导致精度丢失
犯过的错
板子
点分治
注意区分原树中传参得到的根节点,与选重心得到的新根节点
点双
注意特判仅有一个节点的点双
大多数题目,缩点后计算答案考虑度数为1、0的点双
圆方树要开 2 倍空间
可持久化数据结构
每一个操作都对应一个唯一的根,且每个根都对应唯一的操作,不能多个操作对应同一个根
可持久化线段树更新时,记得从原来的版本继承数据
cdq分治
-
归并排序仅需一个数组,不要与整体二分混淆
-
归并过程中统计数据,要统计从区间 \(l\) 开始总计数据
正确的:(cnt统计总和)
int cnt = 0;
for (i = l, j = mid + 1; j <= r; ) {
while (i <= mid && t2d[i].c <= t2d[j].c) {
if (!t2d[i].tag) cnt += w[t2d[i].id];
t3d[idx++] = t2d[i++];
}
if (t2d[j].tag) f[t2d[j].id] += cnt;
t3d[idx++] = t2d[j++];
}
错误的:(应新建变量mx统计\([l, i]\)的最大值)
for (i = l, j = mid + 1; j <= r; ) {
while (i <= mid && us[i].l <= us[j].l) {
tmp[len++] = us[i++];
}
if (i > l) {
maxl[us[j].id] = max(maxl[us[j].id], us[i - 1].l);
}
tmp[len++] = us[j++];
}
-
注意去重
-
注意排序规则,将能贡献给某一元素的其他元素都排在它前面
BSGS
哈希表记得多测清空
特判 \(a=0\),\(p=1\) 的情况
平衡树
FHQ-Treap
有 split 必有 merge,根要及时变化
记得下传标记:merge, split, traverse
Splay
注意 rotate 的节点更新顺序是由下到上,多检查几遍,确认节点都连对了
删除时判断 !cnt[rt] 而不是 !siz[rt]
SA
st 表是 min
多测的时候注意看情况将 rk 或 old 清空到合适的长度,可能要清到 \(n + 1\),以防万一,可以多清几个位置
memcpy(old, rk, (n + 1) * sizeof(int));
old[n+1] = 0; // !!! 注意不要在上一行复制,因为 rk[n+1] 不一定是 0
m = 0;
for (int i = 1; i <= n; i++)
rk[sa[i]] = (i == 1 || old[sa[i]] != old[sa[i - 1]] || old[sa[i] + w] != old[sa[i - 1] + w]) ? ++m : m;
SAM
- 初始化时记得
tot = las = 1 - case 1(第一次加入这个字符) 应该写
lk[cur] = 1而不是lk[p] = 1
线段树
- 下传标记后标记要清空
- 往下递归的时候不要与 \(l\),而应与 \(mid\) 比较
- 下标范围不要无脑写 \([1, n]\),不要无脑写
T.qry(1, 1, n, ...)
SegBeats
- 不要混淆最大(小)值与次大(小)值
- 同时维护 min,max 时,注意不要混淆 min 与 max,不要混淆 +inf 与 -inf,而且需要特殊处理 max=min(区间内只有一种数),max2=min,min2=max(区间内只有两种数) 两种情况
- 可以用 ax, in 分别代替 max, min
- 下传最大值专有的标记时记得只下传到有最大值的子结点
数位 dp 记忆化搜索版本
- 记忆化数组 f 是不考虑 high 和 lead 的结果
- 也要注意初始化
ll dfs(int u, ..., bool high, bool lead)
{
if (u < 1) {
...
return ...;
}
if (!high && !lead && f[u][cons][val] != -1) return f[u][cons][val]; // 注意是!high && !lead
...
if (!high && !lead) f[u][cons][val] = ret;
return ret;
}
Misc
1. 多测忘记清空
LCA的fa数组每次也要清空
BSGS 的哈希表
尤其注意函数内部的 static 对象
2. 数据溢出或取模出错
- 溢出
- 乘法、减法爆 int 或 long long
- unsigned 对象下溢(例如
vec.size()),对于 size,可以
#define size(a) ((int) a.size())
3. 判空、判边界、设初值
- 双指针
!s.empty()- 正确设置哨兵、空元素、初值
4. 数组开小,注意算好数组大小
- 线段树开 4 倍空间
- Manacher 开 2 倍空间(因为要补井号)
5. 注意运算优先级(尤其位运算)
| 类别 | 运算符 | 结合顺序 |
|---|---|---|
| 后缀 | () [] -> . ++ - - | 从左到右 |
| 一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
| 乘除 | * / % | 从左到右 |
| 加减 | + - | 从左到右 |
| 移位 | << >> | 从左到右 |
| 关系 | < <= > >= | 从左到右 |
| 相等 | == != | 从左到右 |
| 位与 AND | & | 从左到右 |
| 位异或 XOR | ^ | 从左到右 |
| 位或 OR | | | 从左到右 |
| 逻辑与 AND | && | 从左到右 |
| 逻辑或 OR | || | 从左到右 |
| 条件 | ?: | 从右到左 |
| 赋值 | = += -= *= /= %= >>= <<= &= ^= |= | 从右到左 |
| 逗号 | , | 从左到右 |
摘自菜鸟教程
6. 递归边界忘记return
7. 差分时应倒序遍历数组
for (int i = n; i >= 1; i--)
a[i] = a[i] - a[i-1];
8. 分块时注意下标指的是块编号还是原数组下标
9. 字典树中信息是存在边上的,而不是点上
10. 倍增数组第二维是位数时,注意防止越界
建议略开大一点
int fa[N+5][24];
for (int i = 24; i >= 0; i--) // RE 注意第二维下标最多23
fa[x][i] .....
11. 题目中给出的图可能不连通
12. 局部变量记得初始化
13. 流程控制
- 循环中continue,记得将被跳过的必须操作处理完
- 循环中判定无解,退出后立即判断
CF2150Bint sum = 0, ans = 1; for (int i = mid, k = n % 2 ? 1 : 2; i > 1; i--, k += 2) { if (k - sum < a[i]) { puts("0"); flag = 1; break; } ans = 1ll * ans * C(a[i], k - sum) % M; sum += a[i]; } // 这一行一定要加上!不然可能上面循环中输出一个0,下面if又输出一次 if (flag) continue; // !!! if (n - sum != a[1]) { puts("0"); flag = 1; } if (flag) continue; - 拼包记得一个分支要 return 0
14. 排序cmp函数
-
不能带等号,严格弱序关系
-
如果有多个关键字,需要考虑当第一关键字相同时怎么做,是无需处理,还是应按第二关键字排序,原则是将能贡献给某一元素的其他元素都排在它前面
15. 注意不等式是否带等号
16. 局部变量记得初始化
17. 慎用 memset
18. 警惕内链
结构体里面的成员函数默认内联,有时候容易爆栈,可以用 __attribute__((noinline)) 关掉内联
建议还是多用 vector,在堆上开空间
但是给非递归函数加上 inline 还是挺有用的
19. 函数传参传引用
形参会产生额外的复制开销,如果传入 vector,string 这种东西,一定要传引用
20. Undefined Behaviour (UB)
- 函数求参顺序是未定义的!
所以像insert(read(), read())这种代码,有可能输入2 3,传参顺序是3 2
21. 函数返回值写成 bool
22. cin/cout 关流后不要与 C 风格输入输出混用
C 风格输出,如 printf, puts 等会在 cout 之后输出,导致输出顺序错乱
23. 哈希忘记取模
24. 忘加引用,白修改了
auto 循环中如果要修改变量要写 for (auto &v : vec)

浙公网安备 33010602011771号