S7 调试自救卡

这是什么: 一张常备速查表。不用从头看——卡住的时候按症状翻到对应那一节。

为什么给你: 自学最大的杀手不是"不会做",是"卡住了不知道下一步该干什么",然后就停在那里了。这份卡片的目标是:任何时候你都有下一步可做。


症状总表:先对号入座

你看到的 翻到
一堆红字,程序根本没跑起来 一、编译错误
编译过了,但黑窗口闪一下就没了 / 弹出崩溃提示 三、运行时崩溃
跑出来了,但答案不对 二、答案错了
样例对了,交上去只得了一部分分 四、超时 + (边界数据)
本地对、评测机错 五、本地对评测机错
什么都对,但是 0 分 六、爆零清单

一、编译错误:五类占九成

📝 永远从第一条报错看起。 编译器出错后会连锁反应,后面几十行都是被第一条带歪的,改掉第一条往往全清。

报错关键词 中文意思 十有八九是
expected ';' before 'xxx' 在 xxx 之前少个分号 分号要加在 xxx 前面那一行的末尾。⚠️ 不同版本编译器报的行号可能是漏分号那行,也可能是下一行——认准报错里的 before 'xxx',往 xxx 前面找
'xxx' was not declared in this scope xxx 没定义 变量名拼错、大小写不对、或函数定义写在了 main 后面
expected '}' at end of input 大括号没配对 少了一个 }。按 Ctrl+A 全选后重新缩进一遍,一眼就能看出来
invalid operands of types 'xxx' and 'yyy' 两种类型不能这么运算 string 去做减法、拿 char 当数组下标之类
no matching function for call to 'xxx' 找不到匹配的函数 参数个数或类型不对,比如 sort(a, n) 少写了 a + n

⚠️ was not declared in this scope 最常见的隐藏原因是函数顺序。 C++ 从上往下读,main 里调用的函数必须写在 main 前面(L06 讲过)。

特别提醒:位运算和 << 打架

cout << a & b << endl;

报错会莫名其妙地指向类型问题。原因是 << 的优先级高于 &,编译器读成了 (cout << a) & (b << endl)

cout << (a & b) << endl;      // 套括号

二、答案错了:怎么定位

按这个顺序做,不要跳步。

第 1 步:手玩最小样例(最有效,但最容易被跳过)

把输入缩到你能用纸笔算出答案的规模——n = 1、n = 2、全相同、全不同。然后:

  1. 纸上算一遍,写下正确答案
  2. 程序跑一遍
  3. 不一致 → 进第 2 步;一致 → 换个更极端的小数据再来

📝 90% 的逻辑错误在 n = 1 或 n = 2 时就会暴露。 大数据只会让你看不清。

第 2 步:打印中间变量

在循环里插 cout,把你以为的值打出来看:

for (int i = 1; i <= n; i++) {
    cout << "[debug] i=" << i << " sum=" << sum << endl;   // 临时
    ...
}

⚠️ 调试输出必须加个显眼的前缀(比如 [debug]),交卷前全部删掉。 忘删 = 答案错 = 0 分,这条在爆零清单里。

第 3 步:检查这五个高频错处

查什么 典型错法
循环边界 i < n 写成 i <= n(数组越界);i = 0 写成 i = 1(漏第一个)
初始值 求和的 sum 没清零;打擂台找最大值初始化成 0,但数据里有负数
数据类型 long long 的写了 int(L01 乘方题的坑)
整数除法 a / b 悄悄砍掉了小数;平均数要写 sum * 1.0 / n
特殊输入 n = 0、全部相同、答案是负数、题目允许的最小/最大值

第 4 步:对拍(有暴力解法时最强)

写两个程序:一个是一定对但很慢的暴力,一个是你的正式解法。再写个小程序随机生成数据,让两个程序跑同一份输入,比对输出。

1. baoli.cpp   ——  三重循环也无所谓,只要保证对
2. mine.cpp    ——  你的正式解法
3. gen.cpp     ——  随机造小数据(n 取 5~10 就够)
4. 反复跑,一旦两个输出不一样,立刻就抓到了一组反例

📝 对拍抓到的反例通常很小,可以直接手玩——这就绕回第 1 步了,而且这次你知道该玩哪组数据。


三、运行时崩溃(RE)

黑窗口闪退、弹出"停止工作"、洛谷返回 RE,基本就这三个原因:

原因 怎么查
数组越界 数组开够了吗?题目说 n ≤ 10⁵,你开的是 a[100005] 还是 a[1005]?下标从 1 开始的话有没有多留一格?
除以 0 / 模 0 分母是变量时,它有没有可能是 0?
递归没边界 L06 讲过:漏了终止条件,C++ 不像 Python 会报 RecursionError,它闷声崩溃

⚠️ 大数组必须开在 main 外面。 int a[1000005]; 写在 main 里会撑爆栈空间,直接崩。写在函数外(全局)就没这个问题,而且全局数组自动清零。


四、超时(TLE)

先算,再改

回到 L01 的复杂度预算表:评测机 1 秒约 10⁸ 次简单运算。

n 的范围 能接受的复杂度
n ≤ 20 O(2ⁿ)
n ≤ 500 O(n³)
n ≤ 5000 O(n²)
n ≤ 10⁶ O(n) ~ O(n log n)
n ≤ 10⁹ O(√n)、O(log n) 或 O(1)

📝 先看数据范围倒推你"最多能用几层循环",再决定写法。 写完才发现超时,往往要整个推倒重来。

怎么知道是哪层循环超了

在最内层加个计数器:

long long steps = 0;
for (...) {
    for (...) {
        steps++;
        ...
    }
}
cout << "[debug] steps=" << steps << endl;

跑一遍最大规模的数据,看 steps 是不是远超 10⁸。这比盯着代码猜快得多。

常见的"意外超时"

情况 说明
循环里反复 sort 每次 O(n log n),n 次就是 O(n² log n)。改用 S3 的计数数组"边加边查"
循环条件里调 sqrt / size() 每轮都重算一次。判质数写 i * i <= n,别写 i <= sqrt(n)
cin / cout 读入 10⁶ 级数据 加一行 ios::sync_with_stdio(false);main 开头(⚠️ 加了之后不能再混用 scanf/printf
特殊数据让循环退化 L01 乘方题:a = 1 时循环真的会跑 10⁹ 次。想想有没有哪种输入会让你的循环"停不下来"

五、本地对,评测机错

这类最难查,因为你在本地复现不出来。按可能性排序:

  1. 数组开小了。 本地小数据不越界,大数据越界。先查这个。
  2. int 溢出。 本地样例数字小,大数据爆 int。改 long long 试试。
  3. 没初始化。 局部变量不会自动清零,本地"碰巧"是 0。大数组一律开全局。
  4. sort 的相等元素顺序。S2——本地的顺序和评测机可能不同。
  5. 未定义行为。 空队列调 front()、空栈调 top()cmp 里写了 <=。这类"本地看起来正常"纯属运气。

六、爆零清单(交卷前必查)

复赛最后 30 分钟停止开新题,逐条过这个清单。这些都是"程序完全正确但拿 0 分"的死法。

📝 完整的 freopen 用法和 Dev-C++ 里的测试步骤见 freopen函数用法教程.md,考前再过一遍。


七、最后一条:卡住超过 15 分钟怎么办

自学最怕死磕。设一个闹钟,卡住 15 分钟就换动作

  1. 先去做 S8_真题分级提示.md 里对应题目的提示 1(只告诉你往哪个方向想)
  2. 还不行,看提示 2(给关键的那一步)
  3. 还不行,看提示 3(完整思路,但代码仍然自己写)
  4. 思路看懂了写不出来 → 把卡住的那一行记下来,下次课的答疑窗口直接问

⚠️ 不要直接去搜别人的完整代码。 抄一遍能过,但下次遇到同类题你还是不会——你缺的从来不是代码,是"怎么想到"的那一步。三层提示就是拆开给你看那一步的。

📝 把每次卡住的地方记在一个本子上。 到第 7 次课复盘时,这个本子比任何讲义都值钱——它精确地告诉你,你的短板到底在哪。