第一次用C语言解决"校门外的树"这类模拟题时,我盯着屏幕上"Wrong Answer"的提示整整半小时。明明测试样例能通过,为什么提交后总是出错?后来才发现,问题出在数组下标的一个微小越界上——这种错误在初学者代码中几乎像传染病一样普遍。本文将带你拆解这道经典题目背后隐藏的四个致命陷阱,并提供可直接复用的调试技巧。
假设马路长度L=10000,你会定义多大的数组?很多新手会直觉地写下int c[10000],然后在循环中访问c[10000]——这正是段错误的根源。C语言的数组下标从0开始,这意味着:
c复制// 危险代码示例
int c[10000]; // 有效下标是0~9999
for(int i=0; i<=10000; i++) { // 当i=10000时越界
c[i] = 1;
}
正确做法:数组大小应比最大下标多1。对于L=10000的情况:
c复制int c[10001]; // 包含0~10000共10001个元素
提示:养成定义数组时检查边界的习惯,可以用
#define MAX_L 10001这样的宏来避免魔法数字。
观察题目样例输入:
code复制2
500 3
150 300
100 200
470 471
当处理区域470-471时,以下两种写法有本质区别:
c复制// 写法A:可能遗漏端点
for(i=a; i<b; i++) { c[i] = 1; }
// 写法B:完全覆盖区间
for(i=a; i<=b; i++) { c[i] = 1; }
关键验证:用极端情况测试,比如区间0-0或500-500。如果漏掉等号,这些单点区间会被完全忽略。
参考代码中的int c[10000]={0}看似初始化了数组,但放在while循环内部意味着每组数据都会重新清零。如果误将其移到循环外部:
c复制int c[10001] = {0}; // 错误的放置位置
while(n--) {
// 后续操作会累加上次的结果
}
调试技巧:在每组数据开始前打印数组内容,观察是否残留之前的数据。更可靠的做法是显式重置:
c复制memset(c, 0, sizeof(c)); // 需要#include <string.h>
样例第一行的2容易被误解为马路长度,实际上是测试组数。正确的输入处理逻辑应该是:
常见错误模式:
c复制// 错误示例:误把第一行当作L
int L = 0, M = 0;
scanf("%d%d", &L, &M); // 实际上第一行是组数N
遇到WA或TLE时,按此顺序检查:
数组维度检查
循环边界验证
数据重置确认
输入流匹配
在关键位置插入调试输出,例如:
c复制printf("Processing interval %d-%d\n", a, b);
for(int i=0; i<=l; i++) {
printf("%d ", c[i]);
}
printf("\n");
这能帮你发现:
记得在最终提交前移除这些调试代码,否则可能导致输出格式错误。
虽然本题L≤10000的数据规模不大,但错误的写法仍可能导致超时。检查以下三点:
避免嵌套循环的冗余计算
c复制// 低效写法:重复标记已移除的树
for(i=a; i<=b; i++) {
if(c[i] == 0) {
c[i] = 1;
sum++;
}
}
// 高效写法:直接标记
for(i=a; i<=b; i++) {
c[i] = 1;
}
输入输出加速
对于C语言,使用快速的输入方法:
c复制// 在main函数开头添加
setvbuf(stdin, NULL, _IOFBF, 4096);
setvbuf(stdout, NULL, _IOFBF, 4096);
编译器优化
提交时选择-O2优化选项(在OJ上通常默认开启)
成功通过这道题后,试着挑战以下变种问题:
这些思考能帮助你真正理解数组模拟技术的本质,而不仅是记住一道题的解法。