调试日记
-
调试日记
-
2023/12/13 21:15
题目:P3469 & SP15577(两道题题意完全相同)
表现:读入大样例时 RE。用之前 AC 的代码测大样例,仍然 RE。
分析:发现 RE 的原因是栈空间太小。NOI 规定的栈空间为 512 MB(待查),而 Dev C++ 默认的栈空间较小。这道题用到了 dfs,递归层数较多,导致 RE。
解决方案:加入编译指令
-Wl,--stack=512000000
,将栈空间设成(大约) 512MB,重测时不再 RE。反思:调试时出现了代码在 OJ 上 AC,在本地出问题的情况。此时应考虑编译环境是否配置得当。另外,本次 RE 的返回值为
3221225725
,上网查询可知此返回值代表递归爆栈空间。因此最好对常见的 RE 返回值比较熟悉。附:适合 NOI 系列比赛的编译选项(可能待完善):
-std=c++14 -Wall -Wextra -Wl,--stack=512000000
-
2024/1/8 21:29
题目: CF864F
反思:清空二维
vector
,不能写成for(auto vec : Vec) vec.clear()
而要写成for(int i = 1; i <= n; i++) Vec[i].clear()
,这可能是因为对前一种写法中的vec
的修改不会作用于Vec
。这同样适用于其他 STL 写法中
for(auto x : X)
的情况,对x
的修改都不作用于X
。 -
2024/1/10 22:00
题目:P2055
过程:调试半天,直到终于静下心来从主函数开始阅读代码,才发现是多测不清空。(事实上我本来记得要清空,但是修改代码的过程中新加了几个数组,于是就忘记了清空新加的数组。)
反思:一定要静态查错!!!不是所有问题都能调出来的!!!
静态查错!
-
2024/1/30 20:02
过程:报错
no matching function for call to 'Node::Node()
原因:原代码为:
struct Node { int x, y; Node(int x, int y): x(x), y(y) {} }tmp[5];
这里定义了一个构造函数,但是定义
tmp[5]
时出错了。应改为Node(int x = 0, int y = 0): x(x), y(y) {}
-
2024/2/1 19:43
过程:报错
comparison with string literal results in unspecified behaviour [-Waddress]
原因:报错是因为这样一行代码:
if(opt == "add")
,其中opt
是一个char
数组。这样用==
比较是不可行的。解决方法:使用
strcmp
函数:if(strcmp(opt, "add") == 0)
-
2024/2/3 19:52
题目:链上二次求和
过程:推完式子以后尝试暴力,预期 TLE,结果 WA。下载数据自测发现输出有负数,但我认为这个式子的结果从意义上看绝对是正数,不可能出现负数,于是猜想是爆
int
导致的。但是在#define int long long
之后仍然输出负数。加了一句while(ans < 0) ans += MOD;
就没问题了。分析:原式确实不可能计算出负数,但是取模之后原先的大小关系已经改变了,所以是可以出现负数的!对于负数取模,正确写法是
x = (x % MOD + MOD) % MOD
-
2024/2/11 21:48
题目:P2658 汽车拉力比赛
原因:错误是这几行代码导致的:
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(key[i][j]) {que.push(Point(i, j)); vis[i][j] = true; break;}
这里的
break
本意是退出双层循环,但break
只能退出单层循环。要退出双层循环,据我所知的正确方法有两种:- 用一个
bool
记录是否要退出循环,外层循环开始时判断一下。 - 使用
低俗的goto
。
- 用一个
-
2024/2/11 22:50
题目:P1873 [COCI 2011/2012 #5] EKO / 砍树
原因:错误来自于
lower_bound
。要用lower_bound
或upper_bound
函数获得某个元素在数组中的下标,正确的写法是int pos = lower_bound(arr + 1, arr + n + 1) - arr;
,其中pos
是元素的下标,n
是数组的长度,下标从 1 开始。这里需要和unique
区分。写离散化时,我们常常使用unique
给数组去重。要获得去重后数组的长度len
,写法是int len = unique(arr + 1, arr + n + 1) - arr - 1
。注意这里多了一个-1
。原因是这样的:lower_bound
upper_bound
和unique
的返回值都是地址,把这个地址与数组开头(也就是arr
)相减就可以得到下标/数组长度。不同的是,lower_bound
upper_bound
返回的是满足条件的元素的地址,因此直接相减就可以得到这个元素的下标;而unique
返回的是最后一个不重复元素的下一个元素的地址,因此相减之后再减 1 才是真正的长度。这里犯错正是因为把二者搞混了,多写了一个-1
。 -
2024/2/18 13:35
题目:P1110
原因:这道题需要用 Treap 查询序列中小于等于某值的最大的数或大于等于某值的最小的数。注意这和前驱后继有区别,多了个等于。因此应该这么调用函数:
qpre(val + 1)
和qnxt(val - 1)
(这么写需要保证元素都是整数),这样就能顾及相等的情况。 -
2024/2/21 17:52
题目:P2044
原因:没开
long long
导致 TLE。我也不知道为什么不开long long
会T,反正记得开就对了…… -
2024/2/23 11.06
题目:CF2A
过程:本来写题写累了想写点水题放松一下,结果这都写挂了,吐了……这道题我用了 map 来实现字符串到
int
的映射,但我的字符串是char
数组,所以我的 map 是这样写的:map<char*, int>
。交了发现不对,调试时我让 map 每次查询元素时都输出一下该元素是否存在,然后我发现即使是对于一个从没有出现过的字符串,程序总是输出该串已存在 map 中,我一度以为是灵异事件……最后总算发现了原因:我的 map 的 key 值是char*
,我以为这样写是让字符串作为 key,而实际上是char
指针作为 key!对于不同的字符串,只要是用相同的char
数组存的,它们的指针都是相同的,map 无法区分。要以字符串作为 map 的 key 值,必须用string
!!!😡 -
2024/2/24 15:39
题目:P1707
过程:试图用列表给矩阵(数组)赋值,报错
assigning to an array from an initializer list
。原因:数组只有在初始化时才能用列表赋值,例如这种写法是正确的:
int tmp[] = {0, 3, 1, 3, 3, 1, 1, 1, 1, w, 1, z};
,而这种写法是错误的:int tmp[12]; tmp = {0, 3, 1, 3, 3, 1, 1, 1, 1, w, 1, z};
。 -
2024/2/26 20:42
题目:P4159
原因:递推滚动数组忘记清零。(
虽然这道题看似用不到滚动数组,但我只是做个实验) -
2024/3/2 21:08
题目:P1972
过程:用不带任何优化的输入输出 A 了之后想用快读看看能快多少,写完快读后报错
invalid operands of types 'int*' and 'int' to binary 'operator%'
。原因:首先,这个报错完全不是因为快读快写写错了,但我却想当然地这么觉得,对着快读查错半天没查出来。后来发现报错是这行代码引起的:
writeln(ans)
,其中ans
是一个数组,我忘记加下标了。反思:
1. 如果报错中出现int*
之类带星号的东西,要注意这可能是数组引起的(数组名就相当于一个指针)。
2. 如果对代码的修改造成了报错,报错不一定是修改的主要部分引起的,如本次报错就不是因为快读写错了,因此要注意看看修改的别的地方是否有错误。 -
2024/3/4 19:50
题目:P1019
过程:代码使用了
string
,输出时报错terminate called after throwing an instance of 'std::out_of_range' what(): basic_string::substr: __pos (which is 18446744073709551615) > this->size() (which is 0)
。原因:只是简单的下标写错导致的越界。值得注意的是,
std::out_of_range
是 C++ STL 容器越界特有的异常。此外,这次报错中主函数返回值居然为3
,这还是我第一次见到返回值不为0
或者一长串数字的情况,值得纪念。 -
2024/3/5 13:27
题目:P1379
过程:我犯了与
7
相同的错误:尝试用break
退出双层循环。警钟长鸣。另:写广搜时,用一个lst
数组记录每个状态是从哪个状态转移过来的,取到目标状态时递归输出转移过程,似乎是一个不错的调试方法。 -
2024 3/5 21:00
题目:P2324
原因:
swap(nt[x][y], nt[tx][ty]); if() continue; else if() do something; else do something; swap(nt[x][y], nt[tx][ty]); // 归位
这段代码的本意是先交换
nt
数组中的两个数,经过一堆if
判断后,再把这两个数交换回来,就不会影响后续的操作。但我这里直接使用了continue
,这样就可能没有成功进行归位,从而导致错误。反思:永远要注意你
for
循环中的continue
和break
(参考文献:7;16)。 -
2024/3/6 20:13
题目:P2329
过程:出现死循环,以为是 dfs 写挂了,结果是主函数里二分的 l 和 r 写反了。
-
2024/3/7 20:30
题目:P10200
原因:写部分分用到了快速幂,快速幂函数我使用了
template<typename T> T qpow(T a, int b)
,本来希望结果返回long long
,但我底数写的是2
,这个数被解析成了int
,导致结果爆了。实际上应该写成2ll
。(之前有一次做状压题的时候也有类似的错误:错误地写了1 << 50
而不是1ll << 50
)。 -
2024/3/16 16:50
题目:P10194
过程:WA,下载数据调试,让程序输出一个预处理的数组
nxt
的值,肉眼可见不正确。又使用了之前写的一个部分分代码对拍这个数组的值,发现两份代码输出不一样,但这两份代码求nxt
的部分是完全一样的。后来我把输出nxt
的代码提前了几行,使其一被求出来就输出,发现输出就正确了。原因:数组开小了。这本是一个低级错误,但我通常认为数组开小会导致 RE,然而这次提交却没有一个点 RE,都是 WA,所以我根本没有往数组开小了这方面去想,而是一直在检查主程序。实际上,数组越界是一种未定义行为(UB),它导致的后果是不确定的,不一定会导致 RE,可能虽然越界了,但访问的地址都是可用的,只是不正确罢了。而之所以我把
cout
提前了几行,输出就正确了,原因是求出来的值存的地址没有被再次访问,因此虽然越界,但能够正确输出。 -
2024/3/26 20:16
题目:P3810
原因:结构体构造函数写错了。
struct Node { int x, y, z, op, ans; Node() {} Node(int x=0, int y=0, int z=0, int op=0): x(x), y(y), z(z), op(op) {} }pt[MAXN << 1]; ... int main() { for(int i = 1, x, y, z; i <= n; i++) pt[++tot] = Node(x, y, z, 0); // 这里 pt[i].ans 没有初始化 ... }
这段代码有两个错误:
1. 两个构造函数冲突了。应该把第二个构造函数中所有的=0
去掉。
2. 第二个构造函数没有初始化ans
的值。在主函数中调用这个构造函数时,ans
未被初始化。 -
2024/3/30 16:00
题目:P3517 动态逆序对
原因:这里涉及到二维偏序和三维偏序在处理问题上的一些细节问题(待补)。
-
2024/4/5 14:03
题目:P10317
原因:这本是一道非常简单的模拟题,我却因为一些细节问题调了半天:循环中过早地
continue
,导致一些应有的操作没有被执行,如下:while(T--) { memset(cnt, 0, sizeof(cnt)), memset(chs, 0, sizeof(chs)); cin >> n; int maxn = 0, pos; for(int i = 1; i <= n; i++) cin >> a[i].type, cnt[a[i].type]++; for(int i = 1; i <= n; i++) { a[i].id = i; cin >> a[i].val; if(a[i].type == FS && a[i].val > maxn) maxn = a[i].val, pos = i; } if(cnt[FS] < 1 || cnt[BD] < (8 - min(cnt[JZ], 1) - min(cnt[FS], 3))) { cout << "No" << endl; continue; // 这里判完无解就直接 continue 了,导致后面的输入没有被正确读取 } int suma = maxn, sumb = 0; for(int i = 1, x; i <= 8; i++) cin >> x, sumb += x; ... }
现在循环退出 bug 大家族又增添了一员(7,16,17,23)。
值得注意的是,我在手动测小样例时,就发现我按完 Ctrl+V 程序就终止了运行,而一般情况下要按完回车程序才会终止运行。这其实就是因为输入没有读完导致的,而我当时并没有在意。以后需要注意这个问题。
-
2024/4/8 19:53
题目:P1874
对于形如 \(f_i \gets \max(f_j + cost(i, j)) + 1\) 之类的转移方程,代码不能写成:
for(int i = 1; i <= n; i++) for(int j = 1; j < i; j++) f[i] = max(f[i], f[j] + cost(i, j)) + 1; // 这里 +1 大错特错
而可以写成:
for(int i = 1; i <= n; i++) { for(int j = 1; j < i; j++) f[i] = max(f[i], f[j] + cost(i, j)); f[i]++; }
或:
for(int i = 1; i <= n; i++) { f[i] = 1; for(int j = 1; j < i; j++) f[i] = max(f[i], f[j] + cost(i, j) + 1); }
-
2024/4/18 18:40
题目: P5836
这道题要求 lca,\(n \le 10^6\),然而我在计算
MAXK
即 \(\log_2 n\) 的时候不小心把 \(n\) 当作了 \(10^5\) 来算,导致MAXK
偏小,所以 WA了一个点。数组开小导致 WA 已经不是第一次见了,我也特意检查了数组空间是否开够了,但对于MAXK
的大小我是凭感觉检查的,并没有发现这个错误。以后对于这类需要自己计算得出的数据范围,需要格外小心。 -
2024/4/29 19:51
题目:AT_abc350_g
这道题是启发式合并,写完交上去 T 了,我还在想是不是我启发式合并写假了,一直在盯着
merge
看,最后发现主函数里面忘记初始化并查集了,这是启发式了个寂寞😅总结:查错时不要只盯着一小块地方看
-
2024/4/20 19:53
题目:AT_abc350_g (没错和刚刚那道一样)
书接上回:改完那个错了之后交上去仍然 T,然后发现并查集初始化写错了。原本我一直写的是
for(int i = 1; i <= n; i++) fa[i] = i;
,但这次想试试新东西,用了自带的iota
函数,写成了iota(fa + 1, fa + n + 1, 0);
,但这是不对的!应该把 0 改成 1!总结:还好这不是在考试中遇见的,要不然我可能很难查出错来,因为我不熟悉
iota
的用法。总之,考试时别用自己不熟悉的语法。 -
2024/5/1 00:00
题目:CF1972C
整数二分答案时,不能直接把二分用的
l
或r
直接当作答案。要单独设置一个答案变量,并且只有当check()
函数返回true
时才能更新答案:while(l <= r) { mid = (l + r) >> 1; if(check()) l = mid + 1, ans = mid; // 此处更新答案 ans else r = mid - 1; }
-
2024/5/11
题目:AT_abc353_d
要得到一个整数
a
在十进制下的位数,不要写成ceil(log10(a))
,这样在计算 10 的整数次幂的位数时会少 1。可以写成floor(log10(a))+1
。 -
2024/5/13 20:03
题目:CF148D
这道题的 dp 转移方程写成代码有点长,导致我不小心把一个除号打成乘号,并导致我算出来的某个东西的概率居然大于 1,调了半天才看出来,就差拿着题解一行一行对了……
总结:静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态查错静态
(特别是写了很长的一串式子的时候)
-
2024/5/16 19:13
题目:CF1706E
这道题是 Kruskal 重构树。为了方便,我重构树中新建的第 i 个点的下标直接为 n+i。这题是多测,并且只限制数据量的和的范围,所以如果直接用
memset(arr, 0, sizeof(arr))
清空数组会 T。我一般用 for 循环清空:for(int i = 1; i <= n; i++)
。但这里我搞错了,因为开了双倍空间,所以不能只清空到 n,应该清空到 2*n,这就导致一直 WA,我还一直没看出来到底哪里没清干净。总结:如果用 for 循环清空数组,要注意清空的范围是否足够。
-
2024/5/16 20:29
题目:P4779 (复习)
For people who are facing similar issue of “invalid declarator before” in C++ even if the syntax you wrote looks good, please check for semicolon in previous line.
-
2024/5/16 20:30
题目:P4779 (还是)如果想让
priority_queue
成为小顶堆,应该这样写:struct Node { int u, dis bool operator > (const Node &rhs) const { return dis > rhs.dis; } } priority_queue<Node, vector<Node>, greater<Node>> que;
- 重载大于号,用
greater
。 - 不要加括号:
greater<Node>()
是错的。 - 以后应该整理一下 stl 的这些语法。
- 重载大于号,用
-
2024/5/18 20:??
题目:AT_abc354_e
写 dfs 时要注意:如果用了全局变量表示状态,在 dfs 函数中修改该全局变量后,要记得改回来!
如果想避免这种错误,就尽量不要用全局变量表示状态,除非状态比较大(比如是一个数组),传到函数里开销太大。
-
2024/5/19 21:11
题目:P10511
取模没取干净。
本来我已经很仔细地检查取模了,但是一直过不了(实际上这是一场 OI 赛制比赛中的题,赛时过了“大”样例,没想到比赛结束一看只有 60 分了,悲)。然后一怒之下
define ll __int128
才过了。后来继续检查了一下,发现我确实漏了地方没取模:sum1 = ((sum[rp] - sum[lp-1]) % MOD + (l[lp] - x) * b[lp-1] % MOD + (y - r[rp]) * b[rp+1] % MOD) % MOD;
模数 \(998244353 < 10^9\),本来在我的估算中两个取模后的数相乘是不会爆
long long
的,所以我大胆写出了(l[lp] - x) * b[lp-1] % MOD
这种东西,而正是这里爆了。其实我前面没分析错,两个取模后的数相乘不会爆long long
,关键是这里的l[lp]
和x
都没有取模,而且都是 \(10^{18}\) 量级的。但由于这个式子里的其它变量都已经取过模了,我就下意识地认为这两个变量也取过模了,最终酿成惨剧。总结:
- 这道题推出来的式子有点长,应该合理使用中间变量,把式子拆成小段,否则不便于阅读。
- 如果二元运算符的两个变量之间没有加取模符号,问自己:这两个变量的量级是多少?会爆吗?
-
2024/5/21 21:21
题目:P4316
拓扑排序里一直出错(队列中出现了不该出现的点),对着那几行调了半天,然后发现错误不在那里,在拓扑排序之前的预处理。本来我添加入度为 0 的节点到队列里是这么写的:
for(int i = 1; i <= n; i++) if(!ind[i]) que.push(i);
但我写完以后发现这道题已经给定了起点是 1,所以只要写成
que.push(1)
就可以了。但我没改干净,写成了:
for(int i = 1; i <= n; i++) que.push(1);
看到这里的时候整个人都绷不住了。为什么老是犯这种低级错误???
总结:静态查错;不要只盯着一个地方看,要有全局意识。
-
2024/6/18 13:00?
本地运行结果和 OJ 运行结果不一样。一度以为发生灵异事件。
实际原因:OJ 上开了 O2,而本地没有开。
(但我仍然不知道为什么开了 O2 以后输出会不一样,我这程序貌似没有 UB)解决方案:在编译选项中加入
-O2
开启 O2,以保持和 OJ 的一致性。 -
2024/6/18 13:43
题目:「AHOI / HNOI2018」道路 (Again)
在本地重复运行相同代码,每次结果都不一样,一度以为发生灵异事件
(其实每天都有一堆灵异事件)。实际原因:代码多次运行结果不同,可能是因为访问了野指针(数组越界/变量未附储值)。我的代码中原本有一行
a = a_ - (n-1), b = b_ - (n-1), c = c_ - (n-1);
,然后改成了a = a_, b = b_, c = c_;
,并把a_
,b_
,c_
数组开大了两倍就过了。(但我仍然不知道究竟是哪里越界了)Upd on 13:50:好了现在我知道是哪里越界了,上一条错误大概也是因为这个(数组越界导致 UB,所以开不开 O2 运行结果不同)。怎么老是犯这种 sb 错误??😅😅😅
-
2024/7/1 13:33
处理数轴上的区间最好这么写:
struct Node { long double a; int type; Node (long double a, int t): a(a), type(t) {} bool operator < (const Node &rhs) const { return a == rhs.a ? type < rhs.type : a < rhs.a; // 注意:若 a == rhs.a,要使左端点在前 } };
-
2024/7/1 21:58
注意整除的运算顺序:
a * b / c
和a * (b / c)
并不相等(a
、b
和c
都是整型)。 -
2024/7/13 20:06
宏定义中的变量要用括号括起来,否则展开的时候可能会出错。
#define sqr(x) (x * x)
是错的,而#define sqr(x) ((x) * (x))
才是对的。 -
2024/7/15 10:50
题目:[NOIP2005 普及组] 采药(复习)
dp 中,如果不用滚动数组,要记得把以前计算的结果赋值过来。如果是滚动数组就不用。
这样是错的(提交记录):
for(int i = 1; i <= n; i++) for(int j = w[i]; j <= V; j++) f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + val[i]);
因为 \(f[i][0..w[i]-1]\) 中的元素没有被正确赋值。
这样是对的(提交记录):
for(int i = 1; i <= n; i++) { for(int j = 0; j < w[i]; j++) f[i][j] = f[i-1][j]; // !!!!!!!!!!!!!!!!!!! for(int j = w[i]; j <= V; j++) f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + val[i]); }