算法笔记(三)
3.1 关系表达式与逻辑表达式
计算机的世界由 0 和 1 组成,0 代表假,1 代表真。
一个简单的例子:输入两个整数 a a a 和 b b b,想知道:
1) a a a 是否大于 b b b ?
2) a a a 是否小于或等于 b b b ?
3) a a a 是否不等于 b b b ?
#include<iostream>
using namespace std;
int main (){
int a, b;
cin >> a >> b;
cout << (a > b) << ' ';
cout << (a <= b) << ' ';
cout << (a != b) << endl;
return 0;
}
注意,如果要比较两个数是否相等,使用的是 == 而不是 =,因为一个等号是赋值的意思。有这些关系运算符组成的不是 0 就是 1 的表达式就是关系表达式。
括号的优先级最高,所以计算机会先去计算括号里面的内容,其次是乘除取余,然后是加减。而大于或小于之类的关系运算符优先级还要低一些,相等或不等的优先级更低。
特殊地,一般不会用 == 来判断两个浮点数是否相等,这是因为浮点数可能会产生精度误差。正确的方式是比较这两个数的差值是否小于一定程度。例如,假设 fabs(a - b) < 1e-6成立,就可以认为浮点数 a 和 b 相等。
例题一 数的性质(洛谷P5710)
一些整数可能拥有以下的性质:
- 性质 1:是偶数;
- 性质 2:大于 4 4 4 且不大于 12 12 12。
小 A 喜欢这两个性质同时成立的整数;Uim 喜欢这至少符合其中一种性质的整数;小 B 喜欢刚好有符合其中一个性质的整数;正妹喜欢不符合这两个性质的整数。现在给出一个整数 x x x,请问他们是否喜欢这个整数?
输入一个整数 x ( 0 ≤ x ≤ 1000 ) x(0\le x \le 1000) x(0≤x≤1000)
输出这 4 4 4 个人是否喜欢这个数字,如果喜欢则输出1
,否则输出0
,用空格分隔。输出顺序为:小 A、Uim、小 B、正妹。样例输入
12
样例输出
1 1 0 0
#include<iostream>
using namespace std;
int main (){
int x;
bool p1, p2;
cin >> x;
p1 = x % 2 == 0;
p2 = 4 < x && x <= 12;
cout << (p1 && p2) << ' '; // 两个性质同时成立
cout << (p1 || p2) << ' '; // 两个性质至少一个成立
cout << (p1 ^ p2) << ' '; // 两个性质刚好一个成立
cout << (!p1 && !p2); // 两个性质同时不成立
// cout << !(p1 || p2); // 也可以这样写
return 0;
}
分析:如果需要像题目这样将多个条件复合成一个条件进行判断,在汉语语境下类似“且”“或”“不”的情况,就需要使用逻辑运算符进行连接,这样的表达式称为逻辑表达式。和关系表达式一样,逻辑表达式也是取值 0 或 1。
值得注意的是,此处为了简化问题,将性质 1 和性质 2 存成 bool 类型的变量,其占用 1 字节,只能表示 0 或 1 这两种取值。
在 C++ 中可以使用以下几种逻辑运算符。
1)与运算符:用 && 来判断两个条件是否同时成立。
2)或运算符:用 || 来判断两个条件是否至少有一个成立。
3)异或运算符:用 ^ 表示两个条件是否刚好一个成立、另一个不成立。
4)非运算符:用 !可以将一个条件取反(将其颠倒)。
逻辑运算产生的结果也是 0 (不成立) 或者 1 (成立)。
对于 x%2==0 || 4<x&&x<=12,可以通过加括号使条件变得清晰,也就是 (x % 2 == 0) || ((4 < x) && (x <= 12))。
例题二 闰年判断(洛谷P5711)。输入一个年份(大于1582的整数),判断这一年是否为闰年,如果是,输出 1;否则,输出 0。
分析:被 4 整除是闰年,但被 100 整除不是闰年;而被 400 整除又是闰年。
#include<iostream>
using namespace std;
int main (){
int x;
bool p;
cin >> x;
p = (x % 400 == 0) || (x % 4 == 0) && (x % 100 != 0);
// p = !(x % 400) || !(x % 4) && x % 100; // 也可这样写
cout << p <<endl;
return 0;
}
对于 &&、|| 和 ! 来说,参与计算的元素如果非零,则无论是正数还是负数,都会被视为 1。
对于 x 是不是 正常数 a 的 倍数,只需判断 x % a 与 0 的关系,若需要 x 不是 a 的倍数,则可将 x % a != 0 简化为 x % a,这是因为此时 x % a 非零,为 1;反之,需要 x 是 a 的倍数,则 x % a == 0 与 !(x % a) 等价。
3.2 分支语句
例题一 Apples(P5712)。小 B 喜欢吃苹果。她今天吃掉了 x x x 个苹果。英语课上学到了 apple 这个词语,想用它来造句。如果她吃了 1 个苹果,就输出
Today, I ate 1 apple.
;如果她没有吃,那么就把 1 换成 0;如果她吃了不止一个苹果,别忘了apple
这个单词后面要加上代表复数的s
。你能帮她完成这个句子吗?输入一行一个自然数 x x x,表示吃掉的苹果数。对于所有数据, 0 ≤ x ≤ 100 0\le x \le 100 0≤x≤100。
#include<iostream>
using namespace std;
int main (){
int x;
cin >> x;
cout << "Today, I ate " << x << " apple";
if (x != 0 && x != 1){ // 也可写成 !(x == 0 || x == 1)
cout << "s";
}
cout << "." << endl;
return 0;
}
if 语句的一种用法如下:
if (成立条件表达式){ 当条件成立时需要执行的语句; }
例题二:洛谷团队系统(洛谷P5713)在洛谷上使用团队系统非常方便的添加自己的题目。如果在自己的电脑上配置题目和测试数据,每题需要花费时间 5 5 5 分钟;而在洛谷团队中上传私有题目,每题只需要花费 3 3 3 分钟,但是上传题目之前还需要一次性花费 11 11 11 分钟创建与配置团队。现在要配置 n n n 道题目,如果本地配置花费的总时间短,请输出
Local
,否则输出Luogu
。输入一个正整数 n n n,表示需要配置的题目量。输出一行,一个字符串。如果本地配置花费的总时间短,请输出Local
,否则输出Luogu
。数据保证 1 ≤ n ≤ 100 1 \leq n\leq 100 1≤n≤100。
#include<iostream>
using namespace std;
int main (){
int n;
cin >> n;
if ((5 * n) < (11 + 3 * n)){
cout << "Local" << endl;
} else{
cout << "Luogu" << endl;
}
return 0;
}
if-else语句:
if(成立条件表达式){
当条件成立时需要执行的语句;
} else {
当条件不成立时需要执行的语句;
}
使用大括号括起来的语句称为代码块。特别地,如果需要执行的语句只有一条语句,那么可以省略大括号。
例题二 小洛机器人。输入不同的字符指令,可以得到不同的回复。
#include<iostream>
using namespace std;
int main (){
char opt;
cin >> opt;
switch (opt){
case 'G': cout << "Hello!" << endl;
case 'N': cout << "I'm Xiaoluo." << endl; break;
case 'S': cout << "Sing." << endl; break;
case 'B': case 'Q':
cout << "Bye bye!" << endl;
break;
default: cout << "Sorry.." << endl;
}
return 0;
}
switch-case 语句:
switch (变量名){
case 变量可能的情况1: 执行语句1; break;
case 变量可能的情况2: 执行语句2; break;
…
default : 执行语句 n;
}
break; 语句是使程序跳出switch-case 语句。需要注意的是,如果某种情况运行完后没有break; 语句,程序就会继续执行下一种情况的语句,直到遇到break; 语句或执行完所有语句。如果一开始就没有匹配到任何一种情况,那么就会运行 default 后面的语句。
switch-case 语句的 case 需要的只能是常量,不能是变量。这些常量类型可以是整数,也可以是字符类型等,但不能是浮点数。对于浮点数,则还是要用多重 if 嵌套作为分支判断,且最好不要直接用 == 判断浮点数相等。
3.3 分支嵌套
例题一 肥胖问题(洛谷P5714)。BMI 指数是国际上常用的衡量人体胖瘦程度的一个标准,其算法是 m h 2 \dfrac{m}{h^2} h2m,其中 m m m 是指体重(千克), h h h 是指身高(米)。不同体型范围与判定结果如下:
- 小于 18.5 18.5 18.5:体重过轻,输出
Underweight
;- 大于等于 18.5 18.5 18.5 且小于 24 24 24:正常体重,输出
Normal
;- 大于等于 24 24 24:肥胖,不仅要输出 BMI 值(使用
cout
的默认精度),然后换行,还要输出Overweight
;现在给出体重和身高数据,需要根据 BMI 指数判断体型状态并输出对应的判断。输入共 2 2 2 个浮点数, m , h m, h m,h,分别表示体重(单位为 kg),身高(单位为 m)。输出一行一个字符串,表示根据 BMI 的对应判断。特别地,对于
Overweight
情况的特别处理请参照题目所述。对于所有数据, 40 ≤ m ≤ 120 40\le m \le 120 40≤m≤120, 1.4 ≤ h ≤ 2.0 1.4 \le h \le 2.0 1.4≤h≤2.0。 m m m 和 h h h 的小数点后不超过三位。
#include<iostream>
using namespace std;
int main (){
double m, h, BMI;
cin >> m >> h;
BMI = m / h / h;
if(BMI < 18.5)
cout << "Underweight";
else if (BMI < 24)
cout << "Normal";
else{
cout << BMI <<endl;
cout << "Overweight";
}
return 0;
}
例题二 三个数排序(洛谷P5715)。给出三个整数 a , b , c ( 0 ≤ a , b , c ≤ 100 ) a,b,c(0\le a,b,c \le 100) a,b,c(0≤a,b,c≤100),要求把这三位整数从小到大排序。输入三个整数 a , b , c a,b,c a,b,c,以空格隔开。输出一行,三个整数,表示从小到大排序后的结果。
解法1:枚举所有的情况,注意存在有数相等的情况
#include<cstdio>
using namespace std;
int main (){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a <= b && b <= c)
printf("%d %d %d", a, b, c);
else if(a <= c && c <= b)
printf("%d %d %d", a, c, b);
else if(b <= a && a <= c)
printf("%d %d %d", b, a, c);
else if(b <= c && c <= a)
printf("%d %d %d", b, c, a);
else if(c <= a && a <= b)
printf("%d %d %d", c, a, b);
else printf("%d %d %d", c, b, a);
return 0;
}
解法2:先找出最小的数,后枚举
#include<cstdio>
using namespace std;
int main (){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a <= b && a <= c){
if(b <= c) printf("%d %d %d", a, b, c);
else printf("%d %d %d", a, c, b);
}
else if(b <= a && b <= c){
if(a <= c) printf("%d %d %d", b, a, c);
else printf("%d %d %d", b, c, a);
}
else{
if(a <= b) printf("%d %d %d", c, a, b);
else printf("%d %d %d", c, b, a);
}
return 0;
}
解法三:是我之前用C语言写的,由于只有三个数,找出最大值,最小值后,中间值用三个数的和减去最大值和最小值即可。
#include<stdio.h>
int main (){
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
int max, min;
max = a > b ? a : b;
min = a < b ? a : b;
max = max > c ? max : c;
min = min < c ? min : c;
printf("%d %d %d", min, a+b+c-max-min, max);
return 0;
}
例题三 月份天数(洛谷P5716)。输入年份和月份,输出这一年的这一月有多少天。需要考虑闰年。输入两个正整数,分别表示年份 y y y 和月数 m m m,以空格隔开。输出一行一个正整数,表示这个月有多少天。数据保证 1583 ≤ y ≤ 2020 1583 \leq y \leq 2020 1583≤y≤2020, 1 ≤ m ≤ 12 1 \leq m \leq 12 1≤m≤12。
#include<iostream>
using namespace std;
int main (){
int y, m;
cin >> y >> m;
switch (m){
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
cout << 31;
break;
case 4: case 6: case 9: case 11:
cout << 30;
break;
case 2:
if(!(y % 400) || !(y % 4) && y % 100)
cout << 29;
else cout << 28;
}
return 0;
}
3.4 分支程序设计案例
例题一 不高兴的津津(洛谷P1085,NOIP2004 普及组)。津津上初中了。妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参加妈妈为她报名的各科复习班。另外每周妈妈还会送她去学习朗诵、舞蹈和钢琴。但是津津如果一天上课超过八个小时就会不高兴,而且上得越久就会越不高兴。假设津津不会因为其它事不高兴,并且她的不高兴不会持续到第二天。请你帮忙检查一下津津下周的日程安排,看看下周她会不会不高兴;如果会的话,哪天最不高兴。
输入格式
输入包括 7 7 7 行数据,分别表示周一到周日的日程安排。每行包括两个小于 10 10 10 的非负整数,用空格隔开,分别表示津津在学校上课的时间和妈妈安排她上课的时间。
输出格式
一个数字。如果不会不高兴则输出 0 0 0,如果会则输出最不高兴的是周几(用 1 , 2 , 3 , 4 , 5 , 6 , 7 1, 2, 3, 4, 5, 6, 7 1,2,3,4,5,6,7 分别表示周一,周二,周三,周四,周五,周六,周日)。如果有两天或两天以上不高兴的程度相当,则输出时间最靠前的一天。
#include<iostream>
using namespace std;
int main (){
int t1, t2, maxtime = 8, maxday = 0;
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 1;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 2;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 3;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 4;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 5;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 6;}
cin >> t1 >> t2;
if(t1 + t2 > maxtime){ maxtime = t1 + t2; maxday = 7;}
cout << maxday;
return 0;
}
例题二 买铅笔(洛谷P1909,NOIP2016 普及组)。P 老师需要去商店买 n n n 支铅笔作为小朋友们参加 NOIP 的礼物。她发现商店一共有 3 3 3 种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起见,P 老师决定只买同一种包装的铅笔。商店不允许将铅笔的包装拆开,因此 P 老师可能需要购买超过 n n n 支铅笔才够给小朋友们发礼物。现在 P 老师想知道,在商店每种包装的数量都足够的情况下,要买够至少 n n n 支铅笔最少需要花费多少钱。
输入格式
第一行包含一个正整数 n n n,表示需要的铅笔数量。
接下来三行,每行用 2 2 2 个正整数描述一种包装的铅笔:其中第 1 1 1 个整数表示这种包装内铅笔的数量,第 2 2 2 个整数表示这种包装的价格。
保证所有的 7 7 7 个数都是不超过 10000 10000 10000 的正整数。
输出格式
1 1 1 个整数,表示 P 老师最少需要花费的钱。
#include<iostream>
using namespace std;
int main (){
int n, n1, p1, n2, p2, n3, p3, m1, m2, m3, min;
cin >> n >> n1 >> p1 >> n2 >> p2 >> n3 >> p3;
m1 = !(n % n1) ? n / n1 * p1 : (n / n1 + 1) * p1;
m2 = !(n % n2) ? n / n2 * p2 : (n / n2 + 1) * p2;
m3 = !(n % n3) ? n / n3 * p3 : (n / n3 + 1) * p3;
min = m1 < m2 ? m1 : m2;
min = min < m3 ? min : m3;
cout << min;
return 0;
}
这里使用了问号表达式,其形式为S1 ? S2 : S3,意思是如果 S1 条件成立,那么它的值就是 S2,否则是S3。
本题还可以用 cmath 头文件中的 ceil() 函数,进行向上取整运算。
例题三 ISBN 号码(洛谷P1055, NOIP2008 普及组)。每一本正式出版的图书都有一个 ISBN 号码与之对应,ISBN 码包括 9 9 9 位数字、 1 1 1 位识别码和 3 3 3 位分隔符,其规定格式如
x-xxx-xxxxx-x
,其中符号-
就是分隔符(键盘上的减号),最后一位是识别码,例如0-670-82162-4
就是一个标准的 ISBN 码。ISBN 码的首位数字表示书籍的出版语言,例如 0 0 0 代表英语;第一个分隔符-
之后的三位数字代表出版社,例如 670 670 670 代表维京出版社;第二个分隔符后的五位数字代表该书在该出版社的编号;最后一位为识别码。
识别码的计算方法如下:
首位数字乘以 1 1 1 加上次位数字乘以 2 2 2 ……以此类推,用所得的结果 m o d 11 \bmod 11 mod11,所得的余数即为识别码,如果余数为 10 10 10,则识别码为大写字母 X X X。例如 ISBN 号码0-670-82162-4
中的识别码 4 4 4 是这样得到的:对067082162
这 9 9 9 个数字,从左至右,分别乘以 1 , 2 , … , 9 1,2,\dots,9 1,2,…,9 再求和,即 0 × 1 + 6 × 2 + … … + 2 × 9 = 158 0\times 1+6\times 2+……+2\times 9=158 0×1+6×2+……+2×9=158,然后取 158 m o d 11 158 \bmod 11 158mod11 的结果 4 4 4 作为识别码。
你的任务是编写程序判断输入的 ISBN 号码中识别码是否正确,如果正确,则仅输出Right
;如果错误,则输出你认为是正确的 ISBN 号码。
输入格式
一个字符序列,表示一本书的 ISBN 号码(保证输入符合 ISBN 号码的格式要求)。
输出格式
一行,假如输入的 ISBN 号码的识别码正确,那么输出Right
,否则,按照规定的格式,输出正确的 ISBN 号码(包括分隔符-
)。
#include<cstdio>
using namespace std;
int main (){
char a, b, c, d, e, f, g, h, i, j;
int check;
scanf("%c-%c%c%c-%c%c%c%c%c-%c", &a, &b, &c, &d, &e, &f, &g, &h, &i, &j);
check = ((a - '0') * 1 + (b - '0') * 2 + (c - '0') * 3 + (d - '0') * 4 + (e - '0') * 5 + (f - '0') * 6 + (g - '0') * 7 + (h - '0') * 8 + (i - '0') * 9) % 11;
if((check == 10 && j == 'X')||(check == j - '0'))
printf("Right");
else
printf("%c-%c%c%c-%c%c%c%c%c-%c", a, b, c, d, e, f, g, h, i, check == 10 ? 'X' : check + '0');
return 0;
}