1.2 分支结构 参考代码

P5709 [深基2.习6] Apples Prologue / 苹果和虫子

\(s\) 分钟的总时间,吃一个苹果需要 \(t\) 分钟。那么,在 \(s\) 分钟内能吃掉的苹果数量就是 \(s/t\)

如果 \(s\) 分钟正好是 \(t\) 的倍数,比如 \(s = 200, t = 10\),那么 \(s/t = 20\),正好吃完 20 个苹果。如果 \(s\) 分钟不是 \(t\) 的倍数,比如 \(s = 205, t = 10\),那么 \(s/t = 20.5\)。这意味着吃完了 20 个苹果,并且第 21 个苹果被啃了一半。但题目问的是吃掉了多少个,即使只吃了第一口,这个苹果也不再是“完整的”了,所以也算被“消耗”掉了。因此,只要时间一开始,第 1 个苹果就开始被吃;只要时间超过 \(t\) 分钟,第 2 个苹果就开始被吃……以此类推。所以,被吃掉的苹果数量应该是总时间 s 除以单个时间 t,并向上取整。在整数运算中,计算 \(s/t\) 的向上取整,可以使用经典表达式 (s + t - 1) / t

t = 0 的情况(除零错误):题目中明确指出了“检查以下被零除”。如果 t = 0,意味着吃一个苹果不需要任何时间。这在物理上是不可能的,但在编程中,如果直接用 (s + t - 1) / t 去计算,就会导致“除以零”时的运行时错误(RE)。需要单独处理这种情况,如果 t = 0,可以理解为一瞬间就能吃完所有苹果。只要 s > 0,所有苹果都会被吃完。

计算出的“被吃掉的苹果数”可能会超过初始的苹果总数 m,例如,有 5 个苹果,但时间足够吃掉 10 个。在这种情况下,最多只能吃掉 m 个苹果。所以,实际吃掉的苹果数是 min(m, (s + t - 1) / t)。如果计算出的被吃掉的苹果数大于 m,那么这个减法的结果会是负数。但苹果的数量不能是负数,最少就是 0 个。所以,最终的答案应该是 max(0, m - (s + t - 1) / t)

参考代码
#include <cstdio>
#include <algorithm> // 引入算法库,用于使用 max 函数
using namespace std;
int main()
{
    int m, t, s, ans;
    scanf("%d%d%d", &m, &t, &s);
    if (t == 0) ans = 0; // 处理 t=0 的特殊情况,防止除零错误
    else ans = m - (s + t - 1) / t; 
    printf("%d\n", max(ans, 0));
    return 0;
}

P5710 [深基3.例2] 数的性质

首先,将问题分解为两个基本步骤:

  1. 判断基本性质:对输入的整数 x,判断它是否满足“性质 1”和“性质 2”。
  2. 组合逻辑判断:根据第一步的结果,利用逻辑运算符(与、或、异或、非)来判断是否满足四个人的偏好。

性质 1是偶数。一个整数 x 是偶数,当且仅当它能被 2 整除,即 x 除以 2 的余数位 0。编程实现可以通过条件 x % 2 == 0,这个表达式的结果是一个布尔值(truefalse),在 C++ 中可以当作整数 1 或 0 来使用。

性质 2大于 4 且不大于 12。一个整数 x 满足这个性质,当且仅当 x > 4 并且 x <= 12。编程实现可以通过条件 x > 4 && x <= 12&& 是逻辑与运算符,只有当两边的条件都为真时,整个表达式才为真。

现在,设 p1 为性质 1 的判断结果,p2 为性质 2 的判断结果。接下来,分析四个人的偏好。

  • 两个性质同时成立:编程实现可以通过运算 p1 && p2
  • 至少其中一种性质:编程实现可以通过运算 p1 || p2
  • 刚好有符合其中一种性质:p1p2 中只有一个为真,这正是异或(XOR)的定义。编程实现可以通过运算 p1 ^ p2
  • 不符合这两个性质:等价于“p1p2”的否定,编程实现可以通过运算 !p1 && !p2!(p1 || p2)
参考代码
#include <cstdio>
int main()
{
    int x;
    scanf("%d", &x);
    // 判断性质 1:是否为偶数
    // x % 2 == 0 这个表达式的结果是布尔值 true 或 false
    // 在 C++ 中,当布尔值被赋给整数变量时,true 会被转换为 1,false 会被转换为 0
    int p1 = (x % 2 == 0);
    // 判断性质 2:是否大于4且不大于12
    // && 是逻辑运算符,只有当两个条件都为真时,整个表达式才为真
    // 同样,结果被转换为 1(true) 或 0(false) 存入 p2
    int p2 = (x > 4 && x <= 12);
    printf("%d %d %d %d\n", p1 && p2, p1 || p2, p1 ^ p2, !p1 && !p2);
    return 0;
}

习题:P5711 [深基3.例3] 闰年判断

解题思路

设输入的年份为 year,判断世纪闰年的逻辑表达式为 year % 400 == 0;判断普通闰年的逻辑表达式为 (year % 4 == 0) && (year % 100 != 0)。最终完整的逻辑表达式为 (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)

参考代码
#include <cstdio>
int main()
{
    int year;
    scanf("%d", &year);
    // 注意:year % 4 == 0 && year % 100 != 0 外的括号可以省略,因为 && 的优先级高于 ||
    // 但为了清晰起见,加上括号是好习惯
    printf("%d\n", year % 400 == 0 || (year % 4 == 0 && year % 100 != 0));
    return 0;
}

P5712 [深基3.例4] Apples

阅读题目要求,可以发现输出的句子根据苹果数量 x 可以分为两大类:当 x 是 0 或 1 时,句子格式是 Today, I ate [数量] apple.,这里的 [数量] 会被 0 或 1 替换;当 x 大于 1 时,句子格式是 Today, I ate [数量] apples.,这里的数量会被具体的 x 值替换,并且 apple 后面加了 s

基于上述分析,可以建立一个清晰的 if-else 逻辑结构来解决问题。

参考代码
#include <cstdio>
int main()
{
    int x;
    scanf("%d", &x);
    if (x <= 1) {
        printf("Today, I ate %d apple.\n", x);
    } else {
        printf("Today, I ate %d apples.\n", x);
    }
    return 0;
}

习题:P5713 [深基3.例5] 洛谷团队系统

参考代码
#include <cstdio>
int main()
{
    int n; scanf("%d", &n);
    // 计算本地配置和洛谷团队的总时间
    int lc = n * 5, lg = 11 + n * 3;
    if (lc < lg) {
        printf("Local\n");
    } else {
        printf("Luogu\n");
    }
    return 0;
}

P5714 [深基3.例7] 肥胖问题

可以使用 if-else if-else 结构来清晰地表达三个互斥的区间判断。

在编程中,直接使用 == 来比较两个浮点数是否相等是危险的,因为浮点数在计算机内部的存储可能存在微小的误差。例如,0.1 + 0.2 的结果可能不是精确的 0.3,而是 0.30000000000000004。为了解决这个问题,在比较浮点数 a 和 b 时,通常不直接判断 a == b,而是判断它们的差的绝对值是否小于一个非常小的数 EPS(Epsilon,例如 1e-6),即 abs(a - b) < EPS。同理,a < b 的安全写法是 a < b - EPS 或者 b - a > EPSa <= b 的安全写法是 a < b + EPS

参考代码
#include <iostream>
using namespace std;
const double EPS = 1e-6; // 定义一个非常小的常量 EPS (Epsilon),用于处理浮点数比较时的精度误差
int main()
{
    double w, h; cin >> w >> h;
    double bmi = w / h / h; // 根据公式计算 BMI 指数
    // 核心逻辑:使用 if-else if-else 结构进行多分支判断
    if (bmi < 18.5 - EPS) { // 判断是否为“体重过轻”
        cout << "Underweight\n";
    } else if (bmi < 24 + EPS) { // 如果不是“过轻”,再判断是否为正常,此时已经隐含了 bmi >= 18.5 - EPS 的条件
        cout << "Normal\n";
    } else { // 如果以上都不是,则为“肥胖”
        // 对于肥胖情况,需要先输出BMI值
        // cout 会使用默认的浮点数精度进行输出,默认就是 6 位有效数字
        cout << bmi << "\nOverweight\n";
    }
    return 0;
}

P5715 [深基3.例8] 三位数排序

  • swap 函数可用于交换两个变量

不一定非得穷举每种大小关系,可以假定最终 a 是最小值,b 是中间值,c 是最大值,最终使得 \(a \le b \le c\)

第一步:确保 a 是最小的。可以比较 a 和 b,如果 a 大于 b,就交换它们的值。经过这一步,a 变成了原来 a 和 b 中的较小者。再比较 a 和 c,如果 a 大于 c,就交换它们的值。经过这一步,a 已经和 b, c 都比较过了,所以 a 现在一定是三个数中的最小值。

第二步:确保 b 和 c 的顺序正确。经过第一步,已经把全局最小值放在了 a 中,现在只需要对剩下的 b 和 c 进行排序即可。比较 b 和 c,如果 b 大于 c,就交换它们的值。

经过以上的比较和交换,a 中是最小值,b 是中间值,c 是最大值,完成了三个数之间的排序。

参考代码
#include <cstdio>
#include <algorithm> // 引入 C++ 算法库,用于使用 swap 函数
using namespace std;
int main()
{
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    // 确保 a 是 a 和 b 中的较小者
    // 如果 a 比 b 大,则交换它们的值
    // 执行后,a <= b(原始的b)
    if (a > b) swap(a, b);
    // 确保 a 是 a 和 c 中的较小者
    // 此时的 a 已经是原始 a,b 中的较小值了,再和 c 比较
    // 如果 a 比 c 大,则交换它们的值
    // 执行后,a 就是三个数中的最小值
    if (a > c) swap(a, c);
    // 此时 a 已经是全局最小值,只需要对 b 和 c 进行排序
    // 比较 b 和 c,如果 b 比 c 大,则交换它们的值
    // 执行后,b <= c,并且由于 a 是最小值,得到了 a <= b <= c 的顺序
    if (b > c) swap(b, c);
    // 按从小到大的顺序,输出排序后的三个数
    printf("%d %d %d\n", a, b, c);
    return 0;
}

习题:P4414 [COCI 2006/2007 #2] ABC

解题思路

可以先将这输入的这三个整数从小到大排序,接下来遇到字符 A/B/C 时就对应输出排序后的第一/二/三个数。

参考代码
#include <iostream>
#include <algorithm> // 引入算法库,为了使用 swap 函数

using namespace std;

int main()
{
    int a, b, c; 
    // 从标准输入读取三个整数
    cin >> a >> b >> c;

    // --- 排序 ---
    // 使用简单的交换操作对三个数进行从小到大排序
    // 最终确保 a <= b <= c
    if (a > b) swap(a, b); // 保证 a 是 a 和 b 中较小的
    if (a > c) swap(a, c); // 保证 a 是 a 和 c 中较小的,此时 a 已经是三者中最小的
    if (b > c) swap(b, c); // 最后比较 b 和 c,保证 b 小于 c

    // 经过排序后,变量 a, b, c 分别对应题目描述中的 A(最小值), B(中间值), C(最大值) 
    
    char ch; 
    
    // --- 按指定顺序输出 ---
    // 读取第一个顺序字符
    cin >> ch;
    // 根据第一个字符决定输出哪个数
    if (ch == 'A') cout << a << " ";
    else if (ch == 'B') cout << b << " ";
    else cout << c << " ";

    // 读取第二个顺序字符
    cin >> ch;
    // 根据第二个字符决定输出哪个数
    if (ch == 'A') cout << a << " ";
    else if (ch == 'B') cout << b << " ";
    else cout << c << " ";

    // 读取第三个顺序字符
    cin >> ch;
    // 根据第三个字符决定输出哪个数,并以换行符结束
    if (ch == 'A') cout << a << "\n";
    else if (ch == 'B') cout << b << "\n";
    else cout << c << "\n";

    return 0;
}

习题:P1909 [NOIP2016 普及组] 买铅笔

解题思路

核心约束是“只能购买同一种包装”,并且“不能拆开包装”。这意味着需要对三种购买方案分别计算成本,然后找出其中的最小值。

可以将整个问题分解为三个独立的子问题,每个子问题对应一种包装的铅笔:如果只买第一/二/三种包装,最少需要花多少钱?最后,在这三个花费中取一个最小值,就是最终的答案。

如何计算只买某一种包装的成本,假设一种包装有 a 支铅笔,价格为 b 元。目标是购买的铅笔总数大于等于 n,买的时候只能按“整包”来买,每包 a 支。因此计算最少需要买多少包,这个数量是 \(\lceil n/a \rceil\),也就是向上取整,通过 (n + a - 1) / a 来实现。计算出包数之后,再乘以 b 就是总花费。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int n;
    scanf("%d", &n);
    int a, b;
    scanf("%d%d", &a, &b);
    int ans = ((n + a - 1) / a * b);
    scanf("%d%d", &a, &b);
    ans = min(ans, (n + a - 1) / a * b);
    scanf("%d%d", &a, &b);
    ans = min(ans, (n + a - 1) / a * b);
    printf("%d\n", ans);
    return 0;
}

习题:P1422 小玉家的电费

解题思路

可以用 if-else if-else 结构实现三个分支的计算,不过使用 minmax 函数可以更加简化代码。

假设用电量是 e,电费可以分成三档:

  • 第一档费用:min(150, e) * 0.4463。如果用电量 e 小于 150,则第一档的用电量就是 e;如果用电量 e 大于等于 150,则第一档的用电量就是 150;min(150, e) 这个表达式可以完美地处理这两种情况。
  • 第二档费用:min(max(e - 150, 0), 250) * 0.4663。第二档的用电区间为 151 ~ 400,总共有 250。max(e - 150, 0) 计算超出 150 的部分。如果 e 小于 150,结果为 0。min(..., 250) 则保证了第二档的用电量不会超过 250。
  • 第三档费用:max(e - 400, 0) * 0.5663max(e - 400, 0) 计算超出 400 的部分,如果 e 不足 400,结果为 0。
参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int e; // 总用电量
    scanf("%d", &e);
    double ans = 0;
    // 计算第一档的电费
    // 如果 e > 150,则用电量为 150,否则为 e
    ans += min(150, e) * 0.4463;
    // 计算第二档的电费
    // max(0, e - 150) 计算超出 150 的部分
    // min(..., 250) 保证第二档的用电量不超过 250
    ans += min(max(0, e - 150), 250) * 0.4663;
    // 计算第三档的电费
    // max(0, e - 400) 计算超出 400 的部分
    ans += max(0, e - 400) * 0.5663;
    printf("%.1f\n", ans);
    return 0;
}

习题:P1424 小鱼的航程(改进版)

解题思路

一个高效的方法是按周来计算,将总天数分为第一周的剩余部分之后的部分两部分来处理。

第一部分的计算:从周 \(x\) 到周日,一共有 \(8-x\) 天。如果总天数 \(n\)\(8-x\) 还少,那说明整个过程都在第一周内结束。因此,第一部分需要计算的天数是 first_week_days = min(n, 8 - x)。在这些天里,游泳的天数需要排除掉周六和周日,代码是 max(first_week_days - 2, 0),因为也有可能一开始的 x 就是 6 或 7。

第二部分的计算:首先,从总天数 n 中减去第一部分的 first_week_days,得到剩余的天数 remaining_days,接下来的时间是从周一开始计算的。remaining_days / 7 可以计算出有多少个完整的星期,每个完整的星期,小鱼都会游泳 5 天(周一到周五)。remaining_days % 7 是除掉整星期后剩下的天数,因为是从周一开始的,所以这几天肯定是周一、周二、……。小鱼最多在这些天里游泳 5 天,所以用 min(remaining_days, 5) 来计算这部分游泳的天数。

最后,将两部分计算出的游泳天数相加,再乘以每天 250 公里,就得到了总的游泳距离。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int x, n; // x:开始是星期几,n:总天数
    scanf("%d%d", &x, &n);
    // 计算第一周需要考虑的天数
    // 8 - x 是从周 x 到周日的天数
    // 如果这个 n 比这个天数少,那么就只计算 n 天
    int first_week_days = min(n, 8 - x);
    // 计算剩余的天数
    int remaining_days = n - first_week_days;
    // 计算第一周的游泳天数(星球六和星期日不游泳)
    int swimming_days = max(first_week_days - 2, 0);
    // 计算剩余天数中的游泳天数
    // remaining_days / 7 是完整的星期数,每个星期游泳 5 天
    // remaining_days % 7 是剩余的天数,从周一开始,最多游泳 5 天
    swimming_days += remaining_days / 7 * 5 + min(remaining_days % 7, 5);
    printf("%d\n", swimming_days * 250); 
    return 0;
}

习题:P5717 [深基3.习8] 三角形分类

解题思路

为了方便后续的判断,首先需要确定哪条是长边,哪两条是短边。类似 P5715 [深基3.例8] 三位数排序 中的操作,通过三次 swap 操作,可以确保让三条边变成 \(a \le b \le c\)。这样,ab 就是两条短边,c 就是最长边。这个预处理步骤极大地简化了后续的逻辑。

根据三角形的构成法则(两边之和大于第三边),因为已经使得 c 是最长边,所以只需要检查两条短边之和 a + b 是否大于最长边 c。如果 a + b <= c,则这三条边无法构成三角形,程序输出 Not triangle 并结束。

如果 a + b > c,说明可以构成三角形。接下来进行一系列的判断,并且由于题目要求符合多个条件的要依次输出,所以使用多个独立的 if 语句。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    // 对三条边进行排序,确保 a <= b <= c
    // 这样 c 就是最长边,便于后续判断
    if (a > b) swap(a, b);
    if (a > c) swap(a, c);
    if (b > c) swap(b, c);
    // 检查是否能构成三角形
    // 三角形法则:两边之和必须大于第三边,因为 c 是最长边,所以只需检查 a+b > c
    if (a + b <= c) printf("Not triangle");
    else {
        // 如果能构成三角形,则进行分类判断
        // 根据勾股定理的推论判断是锐角、直角还是钝角三角形
        if (a * a + b * b == c * c) printf("Right triangle\n");
        if (a * a + b * b > c * c) printf("Acute triangle\n");
        if (a * a + b * b < c * c) printf("Obtuse triangle\n");
        // 判断是否为等腰三角形
        // 因为已经排序,只需要检查相邻的边是否相等即可
        if (a == b || b == c) printf("Isosceles triangle\n");
        // 判断是否为等边三角形
        if (a == b && b == c) printf("Equilateral triangle\n");
    }
    return 0;
}

习题:P7909 [CSP-J 2021] 分糖果

解题思路

需要解决的核心问题是:求 \(\max \{ k \bmod n \}\),其中 \(L \le k \le R\)

任何整数对 \(n\) 取模的结果范围是 \([0,n-1]\),因此,能得到的最大奖励糖果数(即最大余数)不可能超过 \(n-1\)

问题在于,\([L,R]\) 这个区间内是否存在一个 \(k\),使得 \(k \bmod n\) 能够取到最大可能的值 \(n-1\),这取决于区间 \([L,R]\) 的长度以及它在模 \(n\) 周期中的位置。

可以根据区间 \([L,R]\) 的长度 \(R-L+1\)\(n\) 的关系进行分类讨论。

情况一:区间长度足够长(\(R-L+1 \ge n\)

当区间内整数的个数大于或等于 \(n\) 时,区间 \([L,R]\) 至少包含了 \(n\) 个连续的整数。在任何 \(n\) 个连续的整数中,它们对 \(n\) 取模的结果必然会覆盖 \(0,1,\dots,n-1\) 所有的可能值。

例如,如果 \(n=7\),区间 \([10,16]\) 长度为 \(7\),包含 \(7\) 个数。这些数对 \(7\) 取模的结果为 \(3,4,5,6,0,1,2\),其中包含了 \(6\)\(n-1\))。

因此,只要区间 \([L,R]\) 足够长,就必然存在一个数 \(k\),使得 \(k \bmod n = n-1\)。由于 \(n-1\) 是可能的最大余数,所以此时答案就是 \(n-1\)

情况二:区间长度较短(\(R-L+1 \lt n\)

当区间的长度小于 \(n\) 时,区间 \([L,R]\) 中包含的数对 \(n\) 取模的结果可能不会覆盖所有 \(0\)\(n-1\) 的值。此时,需要分析 \(L\)\(R\)\(n\) 取模的结果。

\(L' = L \bmod n\)\(R' = R \bmod n\)

  1. 如果 \(L' \le R'\):这种情况意味着 \(L\)\(R\) 在模 \(n\) 意义下处于同一个“上升段”,例如 \(n=10,L=12,R=15\),此时 \(L'=2,R'=5\)。当 \(k\)\(L\) 增加到 \(R\)\(k \bmod n\) 的值也单调地从 \(L'\) 增加到 \(R'\)。因此,最大值在 \(k=R\) 时取得,为 \(R'\),即 \(R \bmod n\)
  2. 如果 \(L' \gt R'\):这种情况意味着从 \(L\)\(R\) 的过程中,模 \(n\) 的值跨越了一个周期(即经过了 \(n-1\) 并回到了 \(0\))。例如 \(n=10,L=8,R=13\),此时 \(L'=8,R'=3\)。当 \(k\)\(8,9,10,11,12,13\),对应的模 \(n\) 的值为 \(8,9,0,1,2,3\)。这个序列先从 \(L'\) 上升到 \(n-1\),然后从 \(0\) 开始再上升到 \(R'\)。显然,最大值是在跨越周期前达到的 \(n-1\)
参考代码
#include <cstdio> 

int main()
{
    int n, l, r;
    // 读取小朋友数量 n,糖果数量下界 L 和上界 R
    scanf("%d%d%d", &n, &l, &r);

    // 目标是找到在 [L, R] 区间内的整数 k,使得 k % n 的值最大。
    // k % n 的最大可能值为 n-1。

    // 情况一:如果区间 [L, R] 包含的整数个数 (r - l + 1) 大于等于 n。
    // 这意味着区间内至少有 n 个不同的整数。
    // 在 n 个连续整数中,它们对 n 取模的结果必然会覆盖 0, 1, ..., n-1 所有值。
    // 因此,一定存在一个 k 使得 k % n = n-1,这是最大可能的余数。
    if (r - l + 1 >= n) {
        printf("%d\n", n - 1);
    } else {
        // 情况二:区间长度小于 n。
        // 此时 k % n 的值不一定能取到 n-1。
        int x = l % n; // 计算区间左端点 L 对 n 的余数
        int y = r % n; // 计算区间右端点 R 对 n 的余数

        // 如果 l % n <= r % n,说明从 L 到 R 的过程中,模 n 的值没有跨越 (n-1)->0 的周期点。
        // 例如 n=10, L=12, R=15。k%n 的序列是 2, 3, 4, 5。最大值在 k=R 时取得,为 R % n。
        if (x <= y) {
            printf("%d\n", y);
        } else {
            // 如果 l % n > r % n,说明从 L 到 R 的过程中,模 n 的值经过了 (n-1)->0 的跳变。
            // 例如 n=10, L=18, R=22。k%n 的序列是 8, 9, 0, 1, 2。
            // 这个序列在跳变前达到了 n-1,所以最大值是 n-1。
            printf("%d\n", n - 1);
        }
    }
    return 0;
}
posted @ 2023-07-24 10:49  RonChen  阅读(98)  评论(0)    收藏  举报