本blog只是记录C++学习以来掉过的一些坑,写下来防止自己下一次再犯,顺便分享
持续更新,到死为止
菜品特套1:vector.size() ‘~’ 的使用
✔ ✔ ✔
for( int i = G[u].size() - 2;i >= 0;i -- )
❌ ❌ ❌
for( int i = G[u].size() - 2;~ i;i -- )
食 用 说 明 书 食用说明书 食用说明书
~:是用来判断是否为-1的简便写法
常见用法👇
跟前向星套用
memset( head, -1, sizeof( head) );
...
for( int i = head[u];~ i;i = nxt[i] )
还有无限输入的判断
此时与EOF产生的作用一样
while( ~ scanf( "%d", &n ) )
//也可写作
while( scanf( "%d", &n ) != EOF )
以上操作一但出现符合
=
−
1
=-1
=−1就会结束程序,跳出循环……
但是倒回去仔细看错误的写法,看似正确实则暗流涌动,一句话就可以直击要害
G [ u ] . s i z e ( ) = = 0 G[u].size()==0 G[u].size()==0的时候怎么办?!! 减完后变成 − 2 -2 −2
此时是判断不出来的,就会一直死循环 − 2 -2 −2下去!
PS:此问题来自哲学的微笑——老刘,疯狂嘲笑哈哈哈哈
菜品特套2:if-else的缩进
✔ ✔ ✔
if( ... ) {
for( int i = 1;i <= n;i ++ )
if( ... )
}
else {
...
}
❌ ❌ ❌
if( ... )
for( int i = 1;i <= n;i ++ )
if( ... )
else
...
食
用
说
明
书
食用说明书
食用说明书
如果
i
f
−
e
l
s
e
if-else
if−else和
f
o
r
for
for是这么操作,那么运行就会很正常
满足
i
f
if
if条件就执行
i
i
i的循环
否则执行
j
j
j的循环
if( ... )
for( int i = 1;i <= n;i ++ )
...
else
for( int j = 1;j <= n;j ++ )
...
但是当我们在循环里面再次嵌套条件语句的时候,此时就问题大大滴了!!
i
f
−
e
l
s
e
用
法
if-else用法
if−else用法
我们要了解
i
f
−
e
l
s
e
if-else
if−else的运行原则,
e
l
s
e
else
else在不加
{
}
\{\}
{}强制区分的时候,是默认否定离它最近的
i
f
if
if情况
也就是说,在错误写法中, e l s e else else否定的是 f o r for for循环里面的 i f if if条件
i f − e l s e if-else if−else是算一条语句的,嵌套在循环里面时,就可以不用打括号
for( int i = 1;i <= n;i ++ )
if( ... ) ...
else ...
//上面写法等价于下面的写法
//如果if,else里面有多条语句的时候
//注意要打括号,不然if-else会被中断,出现CE
for( int i = 1;i <= n;i ++ ) {
if( ... ) ...
else ...
}
i f − e l s e if-else if−else是一条语句,所以中间是不能被其它非 i f − e l s e if-else if−else语句打断
for( int i = 1;i <= n;i ++ )
if( ... ) ...
n = n + 1;
else ...
//这个时候会if-else编译报错,并且提醒我们缺少大括号
往往题目的情况不止简简单单的两种,这个时候我们一样处理
for( int i = 1;i <= n;i ++ )
if( ... ) ...
else if( ... ) ...
else if( ... ) ...
else ...
//两种写法是等价的
for( int i = 1;i <= n;i ++ )
if( ... )
else if( ... ) ...
else if( ... ) ...
else ...
综上,总结一下:
我们的缩进只是为了代码可观,逻辑清晰易懂
但是程序运行有自己严格的规则,并不是智能AI
程序并不会按照我们的缩进,智能分类匹配 i f − e l s e if-else if−else
PS:此问题出自蒟蒻博主,例题“zamjena”,当时调了好几天,自己也发现了加括号和不加括号会有答案的区别,但是当时没有意识到本质的原因是什么
博主自我反思:
有的时候觉得if-else很简洁,而且我自己不太喜欢打大括号,从而可以缩减代码行数
现在细想来觉得还是应该从严谨逻辑的角度出发,还是打个大括号,避免这种分歧
菜品特套3:int范围边界的1ll使用
✔ ✔ ✔
long long n = 1ll << 31 - 1;
//运行结果:2147483647
❌ ❌ ❌
long long n = 1 << 31 - 1;
食 用 说 明 书 食用说明书 食用说明书
有符号整型 i n t : 2 32 int:2^{32} int:232,占用4字节,32bit
然而取值范围 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [−231,231−1]
无符号整数 u n s i g n e d _ i n t unsigned\_int unsigned_int取值范围就可以达到 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,232−1]
C++计算的时候是默认 i n t int int类型,也就是说如果我们不强调 1 l l 1ll 1ll, 1 1 1就只申请了 i n t int int类型
我们可能会以为:我左边储存答案的变量开了
l
o
n
g
l
o
n
g
long\ long
long long啊,这不存的下嘛
其实不然,可以理解为是右边每个计算部分存好后再统一进行操作
然后放到左边变量里面,并不是直接在左边变量里面操作
延伸拓展👇
请看接下来的两种操作写法
int A, B;
long long n = A + B;
long long A;
int B;
long long n = A + B;
将
A
,
B
A,B
A,B赋值
2147483647
2147483647
2147483647
会发现第一种写法溢出了,而第二种写法算出了正确答案
这是因为
A
+
B
A+B
A+B暂时将结果存在了
A
A
A里面,然后再像赋值一样赋给
n
n
n
可以试试
A
:
i
n
t
,
B
:
l
o
n
g
l
o
n
g
A:int,B:long\ long
A:int,B:long long也是一样的
而第一种写法
A
A
A是
i
n
t
int
int类型的,显然存不下,所以就爆出去了
解决方法有两种
第一个就是像第二种写法一样直接把变量开成
l
o
n
g
l
o
n
g
long\ long
long long
第二种就是计算时乘以
1
l
l
1ll
1ll,一般习惯在第一个变量前面加,这样右边整个答案只要在
l
o
n
g
l
o
n
g
long\ long
long long范围内都可以存储
int A, B;
long long n = 1ll * A + B;
泼水不收
long long n = ( 1 << 30 ) + ( 1 << 30 ) - 1;
long long n = ( 1 << 30 ) + 3 - ( 1 << 30 ) - 4;
...
long long n = ( 1 << 30 ) + x - ( 1 << 30 ) - ( x + 1 );
//可以试试这种写法
//x不要带得太大
//发现这种写法一样可以
这是为什么呢??留给dalao们,蒟蒻不懂
蒟蒻bb:
初学C++,学了很多算法
但是对这种知识真的是了解很少,不懂计算机的运算法则
所以经常会在各种歪歪扭扭的角(ka)角(ka)落(guo)落(guo)到处爆掉
PS:
Upd–>2020-06-11
老刘好SB哈哈哈哈,写FWT板题
(
f
[
k
]
+
f
[
k
+
p
]
+
m
o
d
)
(f[k]+f[k+p]+mod)%mod
(f[k]+f[k+p]+mod)
还不知道为什么错了,要是
f
[
k
]
=
f
[
k
+
p
]
=
m
o
d
−
1
f[k]=f[k+p]=mod-1
f[k]=f[k+p]=mod−1
三个加一起就爆
i
n
t
int
int了呀,我看了一眼就发现了哈哈哈哈哈
菜品特套4:内存计算
做
F
M
T
FMT
FMT州区划分,给爷调崩了,一直
R
E
RE
RE,我都以为是被针对了!!
✔
✔
✔
long long dp[23][1 << 22], g[23][1 << 22], inv[1 << 22];
❌ ❌ ❌
long long dp[23][1 << 21], g[23][1 << 21], inv[1 << 21];
食 用 说 明 书 食用说明书 食用说明书
1 G B = 1024 M B = 1024 ∗ 1024 K B = 1024 ∗ 1024 ∗ 1024 B ( b y t e 字 节 ) 1GB=1024MB=1024*1024KB=1024*1024*1024B(byte 字节) 1GB=1024MB=1024∗1024KB=1024∗1024∗1024B(byte字节)
在 C++ 中
i n t int int类型每个空间是 4 4 4个字节
l o n g l o n g long\ long long long是 8 8 8个字节
b o o l bool bool类型是 1 1 1个字节
数组占用内存的计算
①: a [ x ] [ y ] a[x][y] a[x][y]的空间大小 = x ∗ y =x*y =x∗y(数组大小) ∗ 4 *4 ∗4(转化为 B y t e Byte Byte) / 1024 /1024 /1024(转化为 K B KB KB) / 1024 /1024 /1024(转化为 M B MB MB)
②:直接用 s i z e o f ( a ) sizeof(a) sizeof(a),这样算出来的空间占存的单位是byte
看到这里的时候就已经明白自己是如何死得死翘翘的了
就以
R
E
RE
RE的数组大小来举栗计算
23
∗
(
1
<
<
22
)
=
96468992
∗
4
(
B
)
/
1024
=
376832
(
K
B
)
/
1024
=
368
M
B
23*(1<<22)=96468992*4(B)/1024=376832(KB)/1024=368MB
23∗(1<<22)=96468992∗4(B)/1024=376832(KB)/1024=368MB
按照
i
n
t
int
int的字节数来计算就已经如此之大,更何况开的是
l
o
n
g
l
o
n
g
long\ long
long long,内存算出来就要
×
2
×2
×2
涉及内存的一般都是卡你数据结构啊,或者状压
d
p
dp
dp…
我认为学习C++还是有必要掌握这么基本的内存计算方法,因为编译器的不同对数组的最大忍受也不同,可能编译能过,但是交上去跑出来是RE,这个时候调起来就会很崩溃...
菜品特套5:全局变量和局部变量的重复
✔ ✔ ✔
#include <cstdio>
int n;
void calc() {
for( int i = 1;i <= n;i ++ )
...
}
int main() {
n = ...
return 0;
}
❌ ❌ ❌
#include <cstdio>
int n;
void calc() {
for( int i = 1;i <= n;i ++ )
...
}
int main() {
int n = ...
return 0;
}
食
用
说
明
书
食用说明书
食用说明书
太智障了,这个坑,一点都不想说fa
做的FMT遗失的答案掉的坑,凸(艹皿艹 )
局部变量只在局部内有值
全局变量适用于全局
局部内可以正确调用局部变量
局部外的其他版块一旦涉及到该变量,默认调用全局
一言以蔽之:局部内优先调用局部内的变量,次调用全局变量
所以错误写法中
c
a
l
c
calc
calc版块的
n
n
n其实是等于0的,该
f
o
r
for
for循环根本未被执行
报告完毕!
菜品特套6:顺序结构&& ||前后顺序
✔ ✔ ✔
bool vis[n + 5];
while( x <= n && vis[x] ) x += y;
❌ ❌ ❌
bool vis[n + 5];
while( vis[x] && x <= n ) x += y;
食
用
说
明
书
食用说明书
食用说明书
顺序结构大师上线
在我初学C++的时候就曾经犯过这个问题,没想到时隔多年再次煞笔
由 & & ∣ ∣ \&\&\ || && ∣∣连接的顺序结构
一连串的条件限制,程序默认从左往右顺次判定
所以在错误写法中,极有可能
v
i
s
[
x
]
vis[x]
vis[x]就已经炸出
v
i
s
vis
vis的范围了,看都没看后面的条件限制
最终导致程序死亡
菜品特套7:scanf(“%s”)与换行符的爱恨情仇
爷爷/奶奶你追的博客,关注的大大更新了
因为真的被玄学到了,所以单独开了一篇blog来写
这里直接上链接吧
五星菜品⑦
菜品特套8:break
原料来自于此blog题解的第二题
这里不谈正解,只考虑用
m
a
p
map
map暴力搞的写法 因为我考场就是纯map
✔
✔
✔
for( int i = 1, x;i <= m;i ++ ) {
scanf( "%d", &x );
long long ans = 0;
bool no = 0;
for( int j = 1;j <= x;j ++ ) {
cin >> s;
if( mp[s] ) ans += mp[s];
else no = 1;
}
if( no ) printf( "-1\n" );
else printf( "%lld\n", ans );
}
❌ ❌ ❌
for( int i = 1, x;i <= m;i ++ ) {
scanf( "%d", &x );
long long ans = 0;
bool no = 0;
for( int j = 1;j <= x;j ++ ) {
cin >> s;
if( mp[s] ) ans += mp[s];
else { no = 1; break; }
}
if( no ) printf( "-1\n" );
else printf( "%lld\n", ans );
}
食
用
说
明
书
食用说明书
食用说明书
想必dalao看一眼就知道咋回事了
没错
b r e a k break break:结束当前循环
c o n t i n u e : continue: continue:结束当前情况,并不结束当前循环
举个栗子
for( int i = 1;i <= n;i ++ )
if( i & 1 ) break;
for( int i = 1;i <= n;i ++ )
if( i & 1 ) continue;
b
r
e
a
k
break
break,
i
=
1
i=1
i=1就直接结束循环了,所以时间复杂度只有
1
1
1
c
o
n
t
i
n
u
e
continue
continue,
i
i
i还是会把
n
n
n以内的数都遍历一遍,时间复杂度仍为
O
(
n
)
O(n)
O(n)
再多说几句, b r e a k , c o n t i n u e . . . break,continue... break,continue...这种语句,跟其他语句混用必须打大括号,不认逗号
如果每一条语句之间都是逗号
计算机会一直读,默认到最后分号出现的位置为一整条语句
所以就不用打大括号
但套上这种专有语句后,就不行了 我也不造为什么
举个栗子
✔
for( int i = 1;i <= n;i ++ )
if( i & 1 ) ans ++, p[++ cnt] = i;
✔
for( int i = 1;i <= n;i ++ )
if( i & 1 ) { ans ++, p[++ cnt] = i; continue; }
❌
for( int i = 1;i <= n;i ++ )
if( i & 1 ) ans ++, p[++ cnt] = i, continue;
转回正题
因为我边读入边线性求解,发现是穷人后就直接
b
r
e
a
k
break
break,导致当前组数据压根没读完
再加之这道题是多组数据,被我中间插断的数据就被我的计算机读入成为下一组数据了
菜品特套9:,;的差距
我以为许久不更新以后就不会写sb锅了
✔ ✔ ✔
#include <cstdio>
void solve( int x ) {
printf( "%d\n", x );
}
int main() {
int cnt = 1;
int pre = ++ cnt; solve( cnt );
printf( "%d", pre );
return 0;
}
/*
输出:
2
2
*/
❌ ❌ ❌
#include <cstdio>
void solve( int x ) {
printf( "%d\n", x );
}
int main() {
int cnt = 1;
int pre = ++ cnt, solve( cnt );
printf( "%d", pre );
return 0;
}
/*
输出:
2
*/
食
用
说
明
书
食用说明书
食用说明书
可能似个人就看出来了,错误写法中
i
n
t
int
int变量名的定义和函数调用中间用的逗号连接
之前也不是没犯过因为逗号死掉的问题,这次又中了
逗号连接的在计算机里的默认是一句话
所以计算机将我的函数调用理解为了int solve(cnt),然后。。。。p都没干
菜品特套10:STL的map/set
s
e
t
set
set:自动去重——解决方法,重载排序
m
a
p
map
map:本质也是一个排序的,不然为什么会有unorderd_map??
所以如果传入的下标是多个元素,必须重载排序,才能编译成功
并且每一个元素都要参与排序
比如
(
1
,
2
,
3
)
(1,2,3)
(1,2,3),
(
1
,
2
,
4
)
(1,2,4)
(1,2,4)
只比较前两位参与排序,那么
s
e
t
set
set自动去重了只留下一个,
m
a
p
map
map则判断两者为一样的
灰常感谢香香mm的倾情赞助,真是让吾辈受益匪浅呢!!%%%%
菜品特套11:C++的执行顺序
前言:是在最近做的线段树优化建图时碰到的
addedge( u, ++ cnt );
addedge( cnt, ++ cnt );
addedge( v, cnt );
这三条语句写者的想法是, u u u向 c n t cnt cnt点连边,然后 c n t cnt cnt向 + + c n t ++cnt ++cnt( c n t cnt cnt先自加 1 1 1)连边, v v v向 c n t + 1 cnt+1 cnt+1连边
e.g.
c
n
t
=
1
cnt=1
cnt=1,
u
u
u向
1
1
1连边,
1
1
1向
2
2
2连边,
v
v
v向
2
2
2连边
你就可以发现Dev-c++被卡RE了
因为
c++一条语句其实是从右往左读的
也就是说在第二条加边语句中,是先 c n t + + cnt++ cnt++再 c n t cnt cnt与 c n t cnt cnt连边
这就连出了自环的无限循环了
所以必须这么写
addedge( u, ++ cnt );
addedge( cnt, cnt + 1 );
addedge( v, ++ cnt );
这也能说明,有的时候我们会像下面这么连续赋值
int a, b, c, d;
a = b = c = d = 419;
先执行d=419
,然后c=d
,接着b=c
,最后a=b
,成功赋值四个变量
菜品特套12:顺次处理字符读入
最近都有碰到字符串读入的处理
众所周知,只有空格和换行是非常头疼的
而且现在随着版本的更新,gets()
已经不允许使用了(但是它的功能真的很舒服啊,可以读入空格和换行,哎),可以试试fgets()
,但建议还是别了吧
一般读入字符串的方法如下
- 一个一个字符地读,
ch=getchar()
,scanf("%c",&ch)
好处自然是可以通过条件语句判断空格和换行,做出相应举措 - 一个字符串一个字符串地读,
scanf("%s",s)
,cin>>s
但都是遇到空格或者换行就直接结束了,便于我们直接处理不要空格和换行的题目
但是博主最近的问题是,需要空格和换行,那么就选择了一个一个字符地读
结果总是忘记最后一个字符串是留下来未操作的,因为后面是空,不能触发处理一个字符串的结束条件
经常最后一个字符串被遗忘,导致出错丢分
菜品特套13:传地址的动态开点
最近做了两道题,是需要可持久化字典树的。
而可持久化标准需要动态开点,所以大家都习惯于直接把数组的地址传过去,这样就直接改了数组。
但是字典树的可持久化就不要传了。
void insert( int lst, int &now, char s ) {
now = ++ cnt; t[now] = t[lst];
for( int i = 1;i <= n;i ++ ) {
int c = s[i] - 'a';
t[now].son[c] = ++ cnt;
now = t[now].son[c];
lst = t[lst].son[c];
t[now] = t[lst];
}
}
insert( root[i - 1], root[i], s[i] );
受到可持久化字典树的影响实在太大了。
上面的写法完全是错误的,因为 n o w now now 从始至终都带的是地址,所以看似我们走了 n o w now now 的儿子边,但是其实一直都是 r o o t [ i ] root[i] root[i] 的编号在自加。
只有 l s t lst lst 版本的字典树一直在正确的走。
所以字典树的就不要传地址了,直接改原数组得了。
void insert( int lst, int now, char s ) {
root[now] = ++ cnt;
now = root[now];
lst = root[lst];
t[now] = t[lst];
for( int i = 1;i <= n;i ++ ) {
int c = s[i] - 'a';
t[now].son[c] = ++ cnt;
now = t[now].son[c];
lst = t[lst].son[c];
t[now] = t[lst];
}
}
insert( i - 1, i, s[i] );
菜品特套14:异或判断相等的偷懒
因为本人的码风和个人喜好问题,比起来 !=
我更喜欢 ^
而且少占一个字符。
众所周知 x^x=0
,所以我很喜欢,if(a^b) ...
这种写法。
这倒是没什么问题,因为 if-else
这种判断句又是
bool
\text{bool}
bool 类的,只认
0
/
1
0/1
0/1。
但是写多了就以为本身运算就是个 0 / 1 0/1 0/1 的返回。
比如我的本意是如果 x ≠ y x\ne y x=y 就 + 1 +1 +1,否则 + 0 +0 +0。
写代码就容易写成 ans+=(x^y)
。
这简直就是大错特错!现在的 x^y
明显是个运算了,而不是 [x^y]
的条件判断。
不如老老实实写 !=
,因为这个真的很难调,思维进入一个误区跳出来很困难的欸。