常见小错误 FREQUENTLY MADE MISTAKES IN OI

重点检查对象

  1. 检查板子,仔细核对易错点
  2. 检查清空,哨兵,0 号元素,初值设置等等
  3. 数组大小
  4. 溢出、类型转换、inf 大小
  5. corner: 有重复元素、0、\(n=1\),等等,注意阅读数据范围
  6. 数组下标
  7. 注意判空、判边界,防止越界
  8. 检查流程控制:if, continue, break, ...
  9. \(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;
}

代码习惯

  1. 时刻提醒自己要清空
  2. 注意溢出等问题(尤其是 unsigned 类型下溢)
  3. 注意类型转换,如 a.size() - 1 应改为 (int)a.size() - 1,long long 和 double 比较会转成 double 导致精度丢失

犯过的错

板子

点分治

注意区分原树中传参得到的根节点,与选重心得到的新根节点

点双

注意特判仅有一个节点的点双

大多数题目,缩点后计算答案考虑度数为1、0的点双

圆方树要开 2 倍空间

可持久化数据结构

每一个操作都对应一个唯一的根,且每个根都对应唯一的操作,不能多个操作对应同一个根

可持久化线段树更新时,记得从原来的版本继承数据

cdq分治

  1. 归并排序仅需一个数组,不要与整体二分混淆

  2. 归并过程中统计数据,要统计从区间 \(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++];
}
  1. 注意去重

  2. 注意排序规则,将能贡献给某一元素的其他元素都排在它前面

BSGS

哈希表记得多测清空

\[a^x \equiv b \pmod p \]

特判 \(a=0\)\(p=1\) 的情况

\[x = \begin{cases} 0, & p=1 \\ 1, & p\neq 1,\ a=0,\ b = 0 \\ 0, & p\neq 1,\ a=0,\ b = 1 \\ \text{use BSGS to calculate}, & \text{otherwise} \end{cases} \]

平衡树

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

  1. 初始化时记得 tot = las = 1
  2. case 1(第一次加入这个字符) 应该写 lk[cur] = 1 而不是 lk[p] = 1

线段树

  1. 下传标记后标记要清空
  2. 往下递归的时候不要与 \(l\),而应与 \(mid\) 比较
  3. 下标范围不要无脑写 \([1, n]\),不要无脑写 T.qry(1, 1, n, ...)

SegBeats

  1. 不要混淆最大(小)值与次大(小)值
  2. 同时维护 min,max 时,注意不要混淆 min 与 max,不要混淆 +inf 与 -inf,而且需要特殊处理 max=min(区间内只有一种数),max2=min,min2=max(区间内只有两种数) 两种情况
  3. 可以用 ax, in 分别代替 max, min
  4. 下传最大值专有的标记时记得只下传到有最大值的子结点

数位 dp 记忆化搜索版本

  1. 记忆化数组 f 是不考虑 high 和 lead 的结果
  2. 也要注意初始化
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. 数据溢出或取模出错

  1. 溢出
    • 乘法、减法爆 int 或 long long
    • unsigned 对象下溢(例如 vec.size()),对于 size,可以
      #define size(a) ((int) a.size())

3. 判空、判边界、设初值

  1. 双指针
  2. !s.empty()
  3. 正确设置哨兵、空元素、初值

4. 数组开小,注意算好数组大小

  1. 线段树开 4 倍空间
  2. 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. 流程控制

  1. 循环中continue,记得将被跳过的必须操作处理完
  2. 循环中判定无解,退出后立即判断
    CF2150B
    int 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;
    
  3. 拼包记得一个分支要 return 0

14. 排序cmp函数

  1. 不能带等号,严格弱序关系

  2. 如果有多个关键字,需要考虑当第一关键字相同时怎么做,是无需处理,还是应按第二关键字排序,原则是将能贡献给某一元素的其他元素都排在它前面

15. 注意不等式是否带等号

16. 局部变量记得初始化

17. 慎用 memset

18. 警惕内链

结构体里面的成员函数默认内联,有时候容易爆栈,可以用 __attribute__((noinline)) 关掉内联

建议还是多用 vector,在堆上开空间

但是给非递归函数加上 inline 还是挺有用的

19. 函数传参传引用

形参会产生额外的复制开销,如果传入 vectorstring 这种东西,一定要传引用

20. Undefined Behaviour (UB)

  1. 函数求参顺序是未定义的!
    所以像 insert(read(), read()) 这种代码,有可能输入 2 3,传参顺序是 3 2

21. 函数返回值写成 bool

22. cin/cout 关流后不要与 C 风格输入输出混用

C 风格输出,如 printf, puts 等会在 cout 之后输出,导致输出顺序错乱

23. 哈希忘记取模

24. 忘加引用,白修改了

auto 循环中如果要修改变量要写 for (auto &v : vec)

posted @ 2025-09-15 20:52  zhm0725  阅读(42)  评论(0)    收藏  举报