文章目录
- 2.4 关系的逻辑运算
-
- 本节习题
- 2.5 分支结构应用
-
- 本节习题
2.4 关系的逻辑运算
前面的例题中有一类范围检测问题,例如要判断
x
x
x 是否介于
a
a
a 和
b
b
b 之间(
a
≤
b
a≤b
a≤b),即检测
a
≤
x
≤
b
a≤x≤b
a≤x≤b 是否成立。需要注意的是,在程序中不能直接使用 if 语句检测
a
≤
x
≤
b
a≤x≤b
a≤x≤b 的值,因为程序会先计算
a
≤
x
a≤x
a≤x 的值,然后用这个值再与
b
b
b 进行比较,也就是说
a
≤
x
≤
b
a≤x≤b
a≤x≤b 在程序中等价于
(
a
≤
x
)
≤
b
(a≤x)≤b
(a≤x)≤b。我们知道
a
≤
x
a≤x
a≤x 的值是 bool 型,只有 0 和 1 两种取值,如果
b
b
b 是一个正整数,那么
(
a
≤
x
)
≤
b
(a≤x)≤b
(a≤x)≤b 在程序中将永远为 true。
在程序中要想正确表达范围检测的逻辑,应该检测
a
≤
x
a≤x
a≤x 与
x
≤
b
x≤b
x≤b 是否同时成立。虽然在多分支结构中我们利用 else 子句的特性省去了部分条件的检测,但是并非所有问题都可以利用 else 子句进行简化。因此我们需要学习一种新的运算 —— 逻辑运算,运算符号与运算规则如下表。
| &&(与) | A && B | 如果子表达式 A 和 B 的值都为 true,则结果为 true,否则为 false;如果 A 为 false,则不计算子表达式 B,称为短路 |
| ||(或) | A || B | 如果子表达式 A 和 B 的值都为 false,则结果为 false,否则为 true;如果 A 为 true,则不计算子表达式 B,称为短路 |
| !(非) | !A | 如果子表达式 A 为 true,则结果为 false,否则为 true |
根据上面的规则,判断
x
x
x 是否介于
a
a
a 和
b
b
b 之间(
a
≤
b
a≤b
a≤b)的 C++ 表达式应该是 a <= x && x <= b。由于关系运算优先级高于逻辑与,因此这里并不需要添加括号来提升优先级。
此外,C++ 还允许使用关键字 and、or、not 来表达逻辑运算符 &&、||、!,不过并不是很常用,建议大家采用符号表示。
【小贴士】目前所学的运算符优先级如下(从高到低,同一括号内优先级相同):(逻辑非),(乘、除、取余),(加、减),(大于、大于等于、小于、小于等于),(等于、不等于),(逻辑与),(逻辑或),(赋值、复合赋值)。
【D1067 [21 年 6 月一级] 大写字母的判断】输入一个字符,判断是否是英文大写字母,即是否是 'A' ~ 'Z' 中的一个。如果是英文大写字母,则输出 YES,否者输出 NO。
前面我们了解到字符型数据是可以直接参与关系运算的,并且由于大写英文字母在 ASCII 码表中是连续存储的,结合逻辑运算的规则,解决这个问题的关键代码如下。
if ('A' <= ch && ch <= 'Z') {
cout << "YES\\n";
}
else {
cout << "NO\\n";
}
【P2819 [ABC208A] Rolling Dice】小高有一个六面骰子,每面分别标有 1 到 6 的数字。他想知道投掷
a
a
a 次骰子,是否有可能得到总和为
b
b
b 的点数。如果能够得到总和为
b
b
b 的点数,输出 Yes;否则,输出 No。
每个骰子最小点数是 1,最大点数是 6,于是投掷
a
a
a 次可以得到的最小总和为
a
a
a,最大总和为
6
a
6a
6a,只需要检测
b
b
b 是否介于
a
a
a 到
6
a
6a
6a 之间即可。
if (a <= b && b <= 6 * a) {
cout << "Yes\\n";
}
else {
cout << "No\\n";
}
【P1539 闰年判断】输入一个年份(大于 1582 的整数),判断这一年是否是闰年,如果是输出 1,否则输出 0。
闰年分为两种情况:整世纪年须是 400 的倍数;非整世纪年须是 4 的倍数。两种情况满足任意一种即可,注意第二种情况需要判断这一年不是整世纪年。
if (y % 400 == 0 || y % 100 != 0 && y % 4 == 0) {
cout << "1\\n";
}
else {
cout << "0\\n";
}
由于逻辑与(&&)的优先级高于逻辑或(||),后面两个条件可以不用添加括号。
【P2821 [ABC223A] Exact Price】高桥的钱包里有一个或多个 100 元硬币,没有其他东西。请问钱包里的总金额是否可能是
x
x
x 元?如果高桥钱包里的总金额可能是
x
x
x 元,请输出 Yes;否则,输出 No,
0
≤
X
≤
1000
0≤X≤1000
0≤X≤1000。
这里只需要检测
x
x
x 是否为 100 的倍数即可,注意
x
x
x 有可能为 0,而钱包里的硬币至少有 1 个,所以
x
x
x 等于 0 是不可能的。
if (!x || x % 100 != 0) { // !x 等价于 x == 0
cout << "No\\n";
}
else {
cout << "Yes\\n";
}
本节习题
- D1093 [21 年 9 月一级] 数字判断
- D1221 [22 年 12 月一级] 判断是否存在重复的数
- P2817 [ABC204A] Rock-paper-scissors
- G1185 [GESP2503 二级] 时间跨越
2.5 分支结构应用
与顺序结构类似,接下来通过几个稍微复杂的问题,进一步提升我们的编程能力与解决问题能力。
【D1002 [OpenJudge] 有一门课不及格的学生】给出一名学生的语文和数学成绩,判断他是否恰好有一门课不及格(成绩小于 60 分)。若该生恰好有一门课不及格,输出 1;否则输出 0。
可以定义一个变量 cnt,用于统计不及格科目的数量,最后检测 cnt 的值是否为 1 即可。需要注意的是变量 cnt 需要初始化为 0。
int cnt = 0;
if (a < 60) {
cnt = cnt + 1;
}
if (b < 60) {
cnt = cnt + 1;
}
cout << (cnt == 1 ? "1\\n" : "0\\n");
【D1168 [22 年 6 月一级] 最小的数】依次输入 3 个整数
a
a
a、
b
b
b、
c
c
c,将
a
a
a、
b
b
b、
c
c
c 中的最小值输出。
不妨假设 a 就是最小值,然后分别检测 b 和 c 是否小于 a,在小于 a 的情况下用赋值语句更新 a 的值即可,上述思想方法称为 “打擂台”。
if (b < a) a = b;
if (c < a) a = c;
cout << a;
【D1196 [22 年 9 月一级] 简单排序】依次输入 3 个整数
a
a
a、
b
b
b、
c
c
c,将他们从小到大的顺序输出。
利用打擂台的方法可以找到最小值,如果最初 a 并不是最小值,则会导致原来 a 中的数据丢失,后续步骤将出现错误。为了避免这种情况的出现,我们将打擂台过程中的赋值语句修改为交换操作。这样在经过两个单分支语句之后,变量 a 中就存储了最小值,而 b 和 c 中就是另外两个数值,在 c 小于 b 的情况下只需要交换它们的值即可实现
a
<
b
<
c
a < b < c
a<b<c。
if (b < a) {
t = a, a = b, b = t; // 利用逗号运算符简化代码结构
}
if (c < a) {
t = a, a = c, c = t;
}
if (c < b) {
t = b, b = c, c = t;
}
cout << a << " " << b << " " << c << endl;
【P6031 [COCI 2007/2008 #3] CETIRI】你原本有 4 个数,它们从小到大排序后构成了等差数列。但是现在丢失了一个数,并且其余的三个数的顺序也被打乱了。请你找出第四个数。如果结果不唯一,输出任意一个即可。
按照从小到大排序之后,假设前两项的差为
x
x
x,后两项的差为
y
y
y,
- 如果
x
x
x 大于y
y
y,则说明缺失第 2 项,且y
y
y 是公差; - 如果
x
x
x 小于y
y
y,则说明缺失第 3 项,且x
x
x 是公差; - 如果
x
x
x 等于y
y
y,则说明缺失第 1 项或第 4 项。
int x = b – a, y = c – b;
if (x > y) {
cout << a + y << endl;
}
else if (x < y) {
cout << b + x << endl;
}
else {
cout << c + x << endl; // 输出a – x也是正确的
}
【D1227 [OpenJudge] 鸡兔同笼】一个笼子里面关了鸡和兔子(鸡有 2 只脚,兔子有 4 只脚,没有例外)。已经知道了笼子里面脚的总数
a
a
a,问笼子里面至少有多少只动物,至多有多少只动物。如果没有满足要求的答案,则输出两个 0,中间用一个空格分开。
由于鸡和兔子的脚都是偶数,如果
a
a
a 为奇数,则没有满足要求的答案;否则最少的动物数量应满足兔子数量最多,此时答案为
⌈
a
/
4
⌉
⌈a/4⌉
⌈a/4⌉;最多的动物数量应满足鸡的数量最多,此时答案为
a
/
2
a/2
a/2。
if (a % 2)
printf("0 0\\n");
else
printf("%d %d\\n", (a + 3) / 4, a / 2);
【P2822 [ABC228A] On and Off】小高每天在
s
s
s 点打开房间的灯,在
t
t
t 点关闭房间的灯(使用 24 小时制)。灯亮着的时候可能会跨越两天。请判断在
x
x
x 点 30 分时灯是否是亮着的,
s
≠
t
s≠t
s=t。
一个比较容易想到的方法是根据
s
s
s 与
t
t
t 的大小关系进行分类讨论,
- 如果
s
<
t
s < t
s<t,说明开灯和关灯是在同一天,那么当s
≤
x
<
t
s \\le x < t
s≤x<t 时,灯是开着的; - 如果
s
>
t
s > t
s>t,说明开灯和关灯跨越到了第二天,那么当t
≤
x
<
s
t\\le x < s
t≤x<s 时,灯是关着的。
虽然上面的方法比较清晰易懂,但是代码较为复杂,留给读者自行实现,下面介绍一种计算时差的方法。先用
t
=
t
−
s
t=t-s
t=t−s 计算出每天开灯的时长
t
t
t,用
x
=
x
−
s
x=x-s
x=x−s 计算出目标时间点距离开灯时间点的时间差
x
x
x。由于时间可能会跨越两天,因此两个时间差都有可能是负数,可以给时间差加上 24 之后再对 24 取余来保证时间差都是非负的。接下来只需要检测目标时间点距离开灯时间点的时间差
x
x
x 是否小于开灯时长
t
t
t 即可。
t = (t – s + 24) % 24;
x = (x – s + 24) % 24;
if (x < t) {
cout << "Yes\\n";
}
else {
cout << "No\\n";
}
本节习题
- P2814 [ABC245A] Good morning
- P2816 [ABC201A] Tiny Arithmetic Sequence
- P2196 [ABC103A] Task Scheduling Problem
- P2827 [ABC253A] Median?
- P2796 [ABC207A] Repression
- P1699 [CF6A] Triangle
- D1424 [25 年 12 月一级] 灯塔



