1.1 顺序结构 参考代码

P1001 A+B Problem

这道题是编程竞赛中最经典的入门题目,它的目标是熟悉编程语言的基本输入、计算和输出操作。

  1. 读取输入
    • 首先,程序需要从标准输入流(通常是键盘或评测系统提供的输入文件)中读取两个整数。
    • 这两个整数由空格隔开,需要一种能够处理这种格式的读取方式。
  2. 进行计算
    • 将读取到的两个整数进行加法运算。
    • 由于题目中提到数值范围可能包含负数,且绝对值最大为 \(10^9\),需要选用能够容纳这个范围的数据类型。在 C++ 中,int 类型的范围大约是 \(-2 \times 10^9\)\(2 \times 10^9\),足以容纳输入的数字以及它们的和,所以 int 是合适的。
  3. 输出结果
    • 将计算得到的和输出到标准输出流(通常是屏幕或评测系统指定的输出文件)。
    • 根据题目要求,输出格式必须非常严格,只能输出计算结果,不能有任何多余的提示信息。
    • 输出结果后通常需要跟一个换行符,以确保格式正确。

算法实现

该问题的实现非常直接,不涉及复杂的算法,只考验对编程语言基本库函数的掌握。

在 C++ 中:

  • 可以使用 scanf 函数(来自 <cstdio> 库)来读取格式化的输入。scanf("%d%d", &a, &b); 可以精确地读取两个由空格隔开的整数,并分别存入变量 ab
  • 也可以使用 C++ 的流输入 cin >> a >> b;(来自 <iostream> 库)
  • 加法运算就是简单的 a + b
  • 输出可以使用 printf 函数 printf("%d\n", a + b);,它会将整数 a + b 的值格式化为字符串输出,并附带一个换行符 \n
  • 也可以使用 C++ 的流输出 cout << a + b << "\n"
参考代码
// 引入标准输入输出库,以便使用 scanf 和 printf 函数
#include <cstdio>
// main 函数是 C/C++ 程序的入口点
// 按照竞赛和标准规范,它应该返回一个 int 类型的值
int main()
{
    // 声明两个整型变量 a 和 b,用于存储输入的两个整数
    // int 类型足以存储题目范围内的数值
    int a, b;
    // 从标准输入读取两个整数
    // "%d%d" 是格式化字符串,表示要读取两个十进制整数
    // &a 和 &b 是取地址运算符,告诉 scanf 函数将读取到的值存放到变量 a 和 b 的内存地址中
    scanf("%d%d", &a, &b);
    // 计算 a 和 b 的和,并使用 printf 函数将结果输出到标准输出
    // "%d" 是格式化占位符,表示这里将要输出一个十进制整数
    // "\n" 是换行符,用于在输出结果后换行
    printf("%d\n", a + b);
    // main 函数正常结束,返回 0
    // 返回 0 表示程序成功执行,是良好编程习惯和竞赛要求的体现
    return 0;
}

P5703 [深基2.例5] 苹果采购

参考代码
#include <cstdio>
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d\n", a * b);
    return 0;
}

P5704 [深基2.例6] 字母转换

  1. 理解字符编码(ASCII)
    • 在计算机中,每个字符(如 'a''B''?')都对应一个唯一的整数值,这个值就是它的编码。在 ASCII 编码表中,所有的小写字母('a''z')和所有的大写英文字母('A''Z')都是连续排列的。
    • 例如,如果 'a' 的 ASCII 码是 \(97\),那么 'b' 就是 \(98\)'c' 就是 \(99\),以此类推。同样,如果 'A' 的 ASCII 码是 \(65\),那么 'B' 就是 \(66\)'C' 就是 \(67\)
  2. 发现转换规律
    • 由于小写字母和大写字母都是各自连续的,可以发现一个规律:任意一个小写字母和它对应的大写字母之间的 ASCII 码值之差是一个固定的常数
    • 例如,'a' - 'A' 的值和 'b' - 'B' 的值是完全相同的。
  3. 建立转换公式
    • 基于这个规律,可以推导出通用的转换公式。要将任意一个小写字母 ch 转换为大写,可以:
      • 计算出 ch 相对于 'a' 的“偏移量”。例如,对于 'c',它的偏移量是 'c' - 'a',结果为 \(2\)
      • 将这个偏移量加到 'A' 的基础上,'A' + 2 就会得到 'C'
    • 将以上两步合并,就得到了核心公式:'A' + (ch - 'a')
  4. 编程实现
    • 输入:程序需要读取一个字符
    • 计算:将读取到的字符变量,直接应用上述的算术公式。C++ 语言允许字符类型直接参与整数运算,它会自动使用其底层的 ASCII 码值。
    • 输出:将计算得到的新的 ASCII 码值,再以字符的形式打印出来。
参考代码
#include <cstdio>
int main()
{
    char ch; // 声明一个字符型变量 ch,用于存储输入的小写字母
    scanf("%c", &ch); // "%c" 是 scanf 的格式化字符串,表示要读取一个字符
    printf("%c\n", ch - 'a' + 'A');
    return 0;
}

P5705 [深基2.例7] 数字反转

这道题要求将一个特定格式的浮点数(例如 123.4)进行“数字”上的反转,得到 4.321

一、问题分析与模型选择

  • 直接数学运算?如果尝试用数学方法来处理,比如读取一个浮点数 123.4,然后通过取余、除法等操作来分离每一位数字,过程会非常复杂。因为浮点数在计算机内部的存储方式(二进制浮点数表示法)并不适合进行十进制的位操作,而且处理小数点的位置也很麻烦。
  • 字符串/字符处理:一个更简单、更直接的思路是,不把输入看作一个“数值”,而是看作一个“字符串”或“字符序列”
    • 输入的 123.4 实际上是由五个字符 '1''2''3''.''4' 组成的序列。
    • 需要的输出 4.321 也是由字符 '4''.''3''2''1' 组成的序列。
    • 通过观察可以发现,输出的字符序列恰好是输入字符序列的完全逆序

二、算法流程

基于将输入视为字符序列的思路,算法流程变得非常简单:

  1. 读取输入:将输入的五个字符分别读取并存储起来,由于输入的格式是固定的(三位整数 + 小数点 + 一位小数),可以确定总共有 \(5\) 个字符。
  2. 存储字符:声明五个 char 类型的变量,例如 c1, c2, c3, c4, c5,用来依次存放这五个字符。
  3. 逆序输出:按照与读取时相反的顺序,将这五个变量打印出来,即先打印 c5,然后是 c4c3c2c1

这个方法巧妙地绕过了所有复杂的浮点数运算,将问题简化为了最基础的字符输入和输出操作。

参考代码
#include <cstdio>
int main()
{
    char c1, c2, c3, c4, c5;
    scanf("%c%c%c%c%c", &c1, &c2, &c3, &c4, &c5);
    printf("%c%c%c%c%c\n", c5, c4, c3, c2, c1);
    return 0;
}

P5706 [深基2.例8] 再分肥宅水

一、题目分解

题目要求计算并输出两项内容:

  1. 每名同学分到的饮料量:将总共 \(t\) 毫升,平均分给 \(n\) 名同学。
  2. 总共需要的杯子数:每名同学需要 \(2\) 个杯子,总共有 \(n\) 名同学。

这两个任务互不影响,可以分开思考和计算。

二、第一个任务:计算人均饮料量

  • 数学模型\(人均饮料量 = (总饮料量 t) / (同学人数 n)\)
  • 数据类型选择
    • 总饮料量 \(t\) 是一个实数(可能带小数),所以应该使用浮点数类型来存储,例如 double
    • 同学人数 \(n\) 是一个整数,使用 int 类型即可。
    • 除法运算的结果也应该是浮点数,以保留小数部分。
  • 输出格式:题目要求结果“严格精确到小数点后 3 位”。这意味着在输出时,需要使用特定的格式化指令来控制小数位数。

三、第二个任务:计算总杯子数

  • 数学模型\(总杯子数 = (每人需要的杯子数) \times (同学人数 n)\)
  • 计算:根据题意,每人需要 \(2\) 个杯子,所以表达式为 2 * n
  • 数据类型选择:同学人数 \(n\) 是整数,\(2\) 也是整数,所以它们的乘积也是一个整数,使用 int 类型即可。
  • 输出格式:题目要求输出一个正整数。

四、算法流程

综合以上分析,整个程序的流程非常清晰:

  1. 声明变量:声明一个 double 类型的变量来存储总饮料 t,一个 int 类型的变量来存储人数 n
  2. 读取输入:从标准输入读取一个浮点数和一个整数,分别存入 tn
  3. 计算并输出第一行:计算 t / n,并使用格式化输出指令(如 %.3f)将其打印出来,确保保留三位小数。
  4. 计算并输出第二行:计算 2 * n,并将其以整数形式打印出来。
参考代码
#include <cstdio>
int main()
{
    // 声明一个双精度浮点数变量 t,用于存储总饮料量
    // 使用 double 是因为输入可能是实数,且能保证足够的精度
    double t;
    int n; // 声明一个整型变量 n,用于存储同学的人数
    // 从标准输入读取一个浮点数和一个整数
    // "%lf" 是 scanf 用于读取 double 类型数据的格式化字符串
    // "%d" 用于读取 int 类型数据
    scanf("%lf%d", &t, &n);
    printf("%.3f\n%d\n", t / n, 2 * n);
    return 0;
}

P1425 小鱼的游泳时间

一、问题分析与挑战

直接对小时和分钟分别进行减法操作是可行的,但会遇到一个麻烦:当结束时间的分钟数小于开始时间的分钟数时(例如,从 12:50 到 19:10),需要向小时“借位”。比如,10 - 50 不够减,就要从 19 小时借 1 小时(即 60 分钟)过来,变成 (10+60) - 50,同时小时数要减 1。这种逻辑需要分支结构判断,虽然可行,但不是最简洁的。

二、核心思路:统一单位

一个更通用且更简洁的策略是将所有时间都转换成一个统一的最小单位。在这个问题中,最小的单位是“分钟”。

  1. 全部转换成分钟
    • 可以计算出开始时间 a 时 b 分,距离当天零点(00:00)一共经过了多少分钟,这个总分钟数是 a * 60 + b
    • 同样,计算结束时间 c 时 d 分,距离当天零点一共经过了多少分钟,这个总分钟数是 c * 60 + d
  2. 计算差值:当两个时间点都用“从零点开始的总分钟数”来表示后,计算它们之间的时间差就变成了一个简单的减法运算:\(总时长(分钟) = (结束时间的总分钟数) - (开始时间的总分钟数)\)
  3. 将结果转换回小时和分钟
    • 得到的总时长是一个纯分钟数,题目要求以“e 小时 f 分钟”的格式输出。
    • 可以用这个总分钟数来换算
      • 小时部分 e:等于 总时长(分钟) / 60(使用整数除法,自动舍去小数部分)
      • 分钟部分 f:等于 总时长(分钟) % 60(使用取余运算,得到除以 60 以后的余数)

这个“转换到最小单位 -> 计算 -> 转换回目标格式”的思路,优雅地避免了所有复杂的借位判断,是处理时间、距离、容量等复合单位计算的常用技巧。

参考代码
#include <cstdio>
int main()
{
    int a, b, c, d;
    scanf("%d%d%d%d", &a, &b, &c, &d);
    int t = c * 60 + d - (a * 60 + b);
    printf("%d %d\n", t / 60, t % 60);
    return 0;
}

习题:P1421 小玉买文具

解题思路

直接用“元”和“角”两种单位来计算会很麻烦,所以最简单直接的思路是统一单位

题目中涉及到的最小货币单位是“角”,可以把所有的钱数都转换成“角”来计算,这样就可以避免小数运算,简化问题。

小玉有 a 元 b 角,有 1 元等于 10 角,所以 a 元就等于 a * 10 角,小玉的总钱数就是 (a * 10) + b 角。

一支笔的价格是 1 元 9 角,同样,转换成“角”就是 (1 * 10) + 9 = 19 角。

现在问题变成了:小于有 (a * 10) + b 角钱,每支笔 19 角,最多能买多少支?这是一个简单的整除问题,用总钱数除以单价,然后取整数部分,就可以得到最多能够买的数量。所以,最终的计算表达式是 (a * 10 + b) / 19

参考代码
#include <cstdio>
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d\n", (a * 10 + b) / 19);
    return 0;
}

习题:P3954 [NOIP2017 普及组] 成绩

  • 能用整数运算完成的尽量用整数去实现
解题思路

在编程中,直接处理小数(浮点数)可能会因为精度问题导致错误,而且效率也稍低。一个更好的方法是将百分比转换为最简分数,用整数来进行运算。作业成绩权重 \(20\%\) 等于 \(\dfrac{20}{100}\),化简后是 \(\dfrac{1}{5}\)。小测成绩权重 \(30\%\) 等于 \(\dfrac{30}{100}\),化简后是 \(\dfrac{3}{10}\)。期末考试成绩权重 \(50\%\) 等于 \(\dfrac{50}{100}\),化简后是 \(\dfrac{1}{2}\)

对于输入的作业成绩 \(a\),它对总分的贡献就是 \(\dfrac{1}{5} a\),在 C++ 的整数运算中,这等价于 a / 5。对于输入的小测成绩 b,它对总分的贡献是 \(\dfrac{3}{10} b\),在代码中写作 b / 10 * 3。这里可以这么写是因为题目保证了输入的成绩都是 10 的倍数,所以 b / 10 不会产生小数,可以精确计算。对于输入的期末考试成绩 c,它对总分的贡献是 \(\dfrac{1}{2} c\),等价于 c / 2

将这三部分的分数贡献相加,a / 5 + b / 10 * 3 + c / 2,就得到了最终的总成绩。由于所有计算都是整数运算,结果也是一个整数,符合题目要求。

参考代码
#include <cstdio>
int main()
{
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    printf("%d\n", a / 5 + b / 10 * 3 + c / 2);
    return 0;
}

P5707 [深基2.例12] 上学迟到

首先,需要计算从出门抵达学校,总共需要花费多少分钟。这个耗时主要由两部分组成:

  1. 走路时间:路程为 \(s\) 米,速度为 \(v\) 米/分钟,所以走路时间是 \(s/v\) 分钟。但这里有一个重要的细节,如果 \(s\) 不能被 \(v\) 整除(例如路程 100 米,速度 30 米/分钟),那么 \(100 / 30 = 3.33 \dots\) 分钟。这意味着需要花费 3 分钟多一点的时间,实际要占用 4 个完整的分钟。所以,走路时间必须向上取整。在整数运算中,计算 \(s/v\) 的向上取整,有一个经典的表达式:(s + v - 1) / v
  2. 额外时间:题目中明确指出,路上还要额外花费 10 分钟。

知道了总耗时,也知道了最晚的到校时间是 08:00,现在需要从 08:00 往前推算总耗时那么多分钟。直接在时和分上进行减法,会遇到复杂的借位问题,特别是当需要跨天计算时(比如从 08:00 往前推算 9 个小时)。

P1425 小鱼的游泳时间 类似,解决这个问题的最佳方法是将所有时间都转换成一个统一的最小单位 —— 分钟,并且使用一个“循环”的数轴来表示一天的时间。

可以把一天 24 小时看作一个从 \(0\)\(24 \times 60 - 1 = 1439\) 的分钟环。00:00 对应分钟 \(0\),08:00 对应分钟 \(8 \times 60 = 480\)

最晚到校时间是第 480 分钟,需要花费总耗时分钟,所以,最晚出发时间对应的分钟数就是 480 减去总耗时。

如果总耗时很大,480 减去总耗时可能会变成一个负数。例如,从 08:00 往前推 10 个小时(600 分钟),结果是 -120 分钟。-120 分钟代表什么?它代表“前一天的 00:00 再往前 120 分钟”。为了把它转换成前一天的正确时间,可以利用模运算,一天总共有 \(24 \times 60 = 1440\) 分钟。一个负数时间 t,它在 1440 分钟的循环周期内,等价于 (t % 1440 + 1440) % 1440,这个表达式可以优雅地将正数和负数都映射到 \([0, 1439]\) 的区间内。

最后,将计算出的总分钟数 t,转换回 HH:MM 的格式。小时部分 HHt / 60,分钟部分 MMt % 60,题目要求输出必须是两位,不足的前面补 0。在 printf 中,可以使用 %02d 这个格式化指令来实现。

参考代码
#include <cstdio>
int main()
{
    int s, v;
    scanf("%d%d", &s, &v);
    int cnt = (s + v - 1) / v; // 计算走路总耗时,需要向上取整
    cnt += 10; // 加上额外的 10 分钟
    int t = (480 - cnt + 1440) % 1440; // (a - b + m) % m 是计算 (a - b) mod m 的一个安全方法,对正负数都有效 
    // "%02d" 是格式化占位符,表示:
    // - d: 输出一个十进制整数
    // - 2: 输出的宽度至少为 2 位
    // - 0: 如果不足 2 位,在前面用 0 填充
    printf("%02d:%02d\n", t / 60, t % 60);
    return 0;
}

强制类型转换

在 C++ 编程中,类型转换是将一个数据类型的值转换为另一种数据类型的过程。有时,这种转换是隐式的(例如,将 int 赋值给 double),但有时我们需要显式地告诉编译器我们想要进行转换,这就是“强制类型转换”。

语法

(new_type)expression;
// 或者
new_type(expression);

选择题:若有定义:int a=7; float x=2.5, y=4.7;,则表达式 x+a%3*(int)(x+y)%2 的值是?

  • A. 0.000000
  • B. 2.750000
  • C. 2.500000
  • D. 3.500000
答案

D

表达式中 (int)(x+y) 的优先级最高。首先计算括号内的 x + y2.5 + 4.7 = 7.2 (结果是 float 类型)。然后进行强制类型转换 (int)(int)(7.2) 会截断小数部分,得到整数 7。此时,原表达式变为:x + a % 3 * 7 % 2

*% 具有相同的优先级,按从左到右的顺序计算。首先计算 a % 37 % 3 = 1。表达式变为:x + 1 * 7 % 2。接着计算 1 * 71 * 7 = 7。表达式变为:x + 7 % 2。最后计算 7 % 27 % 2 = 1。表达式变为:x + 1

xfloat 类型,值为 2.51int 类型。在 floatint 混合运算时,int 类型会隐式转换float 类型(即 1 变为 1.0)。计算 2.5 + 1.0,结果为 3.5


选择题:设变量 x 为 float 型且已赋值,则以下语句中能将 x 中的数值保留到小数点后两位,并将第三位四舍五入的是?

  • A. x=(x*100+0.5)/100.0;
  • B. x=(int)(x*100+0.5)/100.0;
  • C. x=(x/100+0.5)*100.0;
  • D. x=x*100+0.5/100.0;
答案

B

核心思想:放大 → 四舍五入取整 → 缩小

将小数点向右移动两位,即将原数乘以 100。这样,需要进行四舍五入的第三位小数就变成了个位数后面的第一位小数。例如,12.345 变为 1234.5,12.342 变为 1234.2。

对放大后的数进行四舍五入,得到一个整数。一个巧妙的方法是“加 0.5 再强制转换为整型”

  • 1234.5 + 0.5 = 1235.0。然后 (int)1235.0 得到 1235。(实现了“五入”)
  • 1234.2 + 0.5 = 1234.7。然后 (int)1234.7 得到 1234。(实现了“四舍”)
  • 这一步是整个逻辑的关键,通过 (int) 类型转换来截断小数部分,从而完成取整。

将上一步得到的整数除以 100.0,将小数点移回原来的位置,得到最终结果。

  • 1235 / 100.0 = 12.35
  • 1234 / 100.0 = 12.34
  • 注意:必须除以 100.0(浮点数)而不是 100(整数),以确保结果是浮点数而不是整数除法。
posted @ 2023-07-23 21:10  RonChen  阅读(84)  评论(0)    收藏  举报