本地测试与调试
为什么要本地测
评测机是排队串行的。高峰期一次提交要等几分钟,你交一次、等三分钟、发现 WA、改一个字、再等三分钟——这个循环一上午能毁掉你三道题的时间。
本地先跑通样例,是基本的效率要求,不是可选项。
输入输出重定向
手动敲输入太慢,尤其是多组数据。用文件重定向:
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ONLINE_JUDGE 宏在评测机上被自动定义,所以这段只在本地生效。
Danger
提交前务必确认这段没在评测机上生效。 如果评测机没定义这个宏而你又写了 freopen,程序会去读一个不存在的文件,结果是全线 RE。交之前扫一眼,这个习惯养成了就不出事,没养成会反复出事。
常用优化开关
数据量到 10⁵ 以上时,cin/cout 会明显拖慢:
ios::sync_with_stdio(false);
cin.tie(nullptr);
Warning
开了这个之后不要混用 scanf/printf 和 cin/cout,两者的缓冲区不同步,输出顺序会乱,而且这种乱是随机出现的,你会查很久。
卡住了怎么定位
本地过、提交 WA
按这个顺序查,别跳:
- 输出格式 —— 行末多余空格、少一个换行、大小写、
Case #1:里的冒号。 - 多组数据的读法 —— 题目是「读到 EOF」还是「第一行给 T」?读错了有时候本地样例刚好能过。
- 数组开小 —— 本地数据弱,没炸;评测机数据强,就炸了。
- 整数溢出 —— 中间结果超过
INT_MAX(2.1×10⁹)要开long long。
这四类占新手 WA 的九成以上。不要一 WA 就怀疑算法,先排这四条。
TLE
先算复杂度,再改代码。别一上来就优化常数。
| n 的范围 | 1 秒内可行的复杂度 |
|---|---|
| n ≤ 10 | O(n!) 看运气 |
| n ≤ 20 | O(2ⁿ) |
| n ≤ 500 | O(n³) |
| n ≤ 5000 | O(n²) |
| n ≤ 10⁶ | O(n log n) |
| n ≤ 10⁸ | O(n) |
复杂度对了还超时,再考虑常数(换快速 IO、去掉 vector 的边界检查、把 cout 换成 printf)。
输出量大的时候,printf 明显比 cout 快。 cout 默认和 C 的 stdio 同步,每次输出都有额外开销;循环里输出十万行时,这个差别就是「过」和「超时」。这也是讲义统一用 printf 的原因之一。
RE
两个最常见的原因:
- 递归爆栈 —— 本地默认栈 8MB,深度递归要手动调。
- 数组越界 —— 本地数据弱,越界的那块内存刚好没被占用,看起来正常。
开 sanitizer 直接抓:
g++ -std=c++17 -fsanitize=address,undefined -g a.cpp -o a
./a
它会在越界或未定义行为发生的那一行报错并退出,比对着 WA 猜快得多。
Info
sanitizer 会让程序慢 2–5 倍、内存占用增加,所以只用它找错,不要用它的耗时判断会不会 TLE。
对拍
怀疑算法对但边界错时,写个暴力程序对拍,比人眼找一小时快:
for i in $(seq 1 1000); do
python3 gen.py > in.txt
./brute < in.txt > out1.txt
./mine < in.txt > out2.txt
if ! diff -q out1.txt out2.txt > /dev/null; then
echo "WA on case $i"
cat in.txt
break
fi
done
关键在 gen.py:要能生成覆盖边界的随机数据(最小值、最大值、全相同、递增递减、n=1)。随手写的随机生成器经常漏掉边界,对吧半天对不出来。
数组开多大
按题目上限再加个余量,别刚好卡边界:
const int N = 1e5 + 10;
int a[N];
刚好开 1e5 而代码里有 a[n + 1],越界。这个 + 10 是买保险,不是浪费。