世界五星级大厨经典菜品集

本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 ifelse 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用法 ifelse
我们要了解 i f − e l s e if-else ifelse的运行原则, 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 ifelse是算一条语句的,嵌套在循环里面时,就可以不用打括号

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 ifelse是一条语句,所以中间是不能被其它非 i f − e l s e if-else ifelse语句打断

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 ifelse

在这里插入图片描述


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} int232,占用4字节,32bit
然而取值范围 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [231,2311]
无符号整数 u n s i g n e d _ i n t unsigned\_int unsigned_int取值范围就可以达到 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321]

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]=mod1
三个加一起就爆 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=10241024KB=102410241024B(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 =xy(数组大小) ∗ 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)=964689924(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] 的条件判断。

不如老老实实写 != ,因为这个真的很难调,思维进入一个误区跳出来很困难的欸。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值