跳转至

第三章 程序的控制结构

基础数学知识

  • 负数、相反数与绝对值:负数小于 \(0\);相反数相加为 \(0\);绝对值表示数到 \(0\) 的距离,例如 \(|-3.14|=3.14\)。对应 1039、1040、1043、1057。
  • 区间与不等式:分清“大于”和“大于等于”,以及端点是否包含。例如 \(1<n<100\) 不包含两端,\(25\le t\le30\) 包含两端。对应 2052、2054、1044、1051、1056。
  • 小数运算与公式代入:会计算小数乘除法;代入公式前,先根据条件选对计算式。对应 2055、2058、1050、1051。
  • 互质与公倍数:\(3\) 与 \(5\) 互质,一个整数同时被两者整除,等价于被它们的乘积 \(15\) 整除。对应 1046。
  • 用余数分类与列式推理:把金额写成 \(x=4n+r\),其中 \(0\le r<4\);每次把 \(4\) 元笔换成 \(5\) 元、\(6\) 元笔,分别多花 \(1\) 元、\(2\) 元。对应 2059。
  • 路程、速度、时间与等式变形:\(t=\frac sv\),总时间还可能包含准备时间;不等式两边同乘正数,大小关系不变。对应 1050。
  • 分段函数与平方:分段函数按自变量所在范围选公式;\((x-3)^2=(x-3)(x-3)\)。对应 1051。
  • 向上取整:不足一份也算一份,例如 \(\lceil501/500\rceil=2\),而 \(\lceil500/500\rceil=1\)。对应 1052。
  • 三角形三边关系:任意两边之和必须严格大于第三边,等于时不能构成三角形。对应 1054。
  • 闰年的判定规则:按题目规则,年份能被 \(4\) 整除且不能被 \(100\) 整除,或能被 \(400\) 整除时,是闰年。对应 1055。
  • 平面直角坐标:用 \((x,y)\) 表示点的位置,横纵坐标同时限制一个区域;本题正方形包含边界。对应 1056。
  • 一元二次方程、平方根与判别式:方程 \(ax^2+bx+c=0\) 中 \(a\ne0\),判别式 \(\Delta=b^2-4ac\) 决定实根情况;\(\Delta\ge0\) 时用 \(x=\frac{-b\pm\sqrt\Delta}{2a}\) 求根。对应 1058。

第一节 if选择结构

2051:【例 3.1】偶数

提示 1:先判断,再决定要不要输出

输入3时,题目有没有要求输出no?

奇数就不输出,别顺手加一句no。

提示 2:把偶数条件放进if

读入整数a后:

if (a % 2 == 0) cout << "yes";

不用补else;用2和3各走一遍。

常见错误

不要习惯性地补上 no,题目没有要求。= 是赋值,比较相等要用 ==。

自测

输入:12 → 预期输出:yes

输入:3 → 预期输出:不输出任何字符

自己试试:自己选一个偶数、一个奇数,各试一次。

先写下哪次应该输出、哪次不该输出,再运行。

参考代码
#include <iostream>

using namespace std;

int main() {
    int a;
    cin >> a;

    if (a % 2 == 0) cout << "yes";

    return 0;
}

2052:【例 3.2】范围判断

提示 1:端点算不算在范围里

把1、2、99、100标在数轴上,哪两个能通过?

“大于1且小于100”需要两件事同时成立。

提示 2:两次比较各写完整
if (n > 1 && n < 100) cout << "yes";

n已读入;不要照数学写成1 < n < 100,不满足时也不用补输出。

常见错误

不能照数学写法写成 1 < n < 100,C++ 会先把 1 < n 算成 0 或 1,再与 100 比较。用 \(1,2,99,100\) 检查端点。

自测

输入:1 → 预期输出:不输出任何字符

输入:2 → 预期输出:yes

输入:99 → 预期输出:yes

输入:100 → 预期输出:不输出任何字符

自己试试:试着各选一个范围内、范围外的数。

除了这两类,两个端点要不要单独检查?

参考代码
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n > 1 && n < 100) cout << "yes";

    return 0;
}

2053:【例 3.3】三个数

提示 1:先固定一种大小关系

把3、1、2从大到小排列,再把两个数换成相同的,是否仍能排?

提示 2:一种顺序对应一个分支

三个整数存入a、b、c,若a最大、b其次:

if (a >= b && b >= c) cout << a << ' ' << b << ' ' << c;

仿照写其他次序,放在else if里;用>=让相等的数也有分支可走。

知识补充

swap(a, b) 会交换两个变量的值,需要 <utility>。也可以用临时变量写成 int t = a; a = b; b = t;,回顾第二章的交换值。 看懂三次比较的作用后,关上提示,换一组数自己写,再检查每次交换后的变量值。

常见错误

不要只用严格大于号列情况,否则两数相等时可能一个分支也进不去。交换法的三次比较要按顺序做;拿 \(1,3,2\) 在纸上跟一遍。

自测

输入:1 3 2 → 预期输出:3 2 1

输入:5 5 2 → 预期输出:5 5 2

输入:-3 -1 -2 → 预期输出:-1 -2 -3

输入:2000000000 2000000000 2000000000 → 预期输出:2000000000 2000000000 2000000000

自己试试:自己选三个数,让其中两个同为最大值。

调换它们的输入顺序,预期输出应该改变吗?

方法一:列出六种大小顺序
#include <iostream>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    if (a >= b && b >= c) cout << a << ' ' << b << ' ' << c;
    else if (a >= c && c >= b) cout << a << ' ' << c << ' ' << b;
    else if (b >= a && a >= c) cout << b << ' ' << a << ' ' << c;
    else if (b >= c && c >= a) cout << b << ' ' << c << ' ' << a;
    else if (c >= a && a >= b) cout << c << ' ' << a << ' ' << b;
    else if (c >= b && b >= a) cout << c << ' ' << b << ' ' << a;

    return 0;
}
方法二:比较并交换
#include <iostream>
#include <utility>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    if (a < b) swap(a, b);
    if (a < c) swap(a, c);
    if (b < c) swap(b, c);

    cout << a << ' ' << b << ' ' << c;

    return 0;
}
方法三:总和减最大值与最小值
#include <iostream>
#include <algorithm>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    int maxn = max(max(a, b), c);
    int minn = min(min(a, b), c);
    long long zhong = 1LL * a + b + c - maxn - minn;

    cout << maxn << ' ' << zhong << ' ' << minn;

    return 0;
}

2054:【例 3.4】适合晨练

提示 1:刚好25度和30度呢

把两端点与中间温度都试一次,题目说的适宜范围包含端点。

提示 2:同时卡住上下界

已读入温度t:

if (t >= 25 && t <= 30) cout << "ok!";

补上不适宜的分支,检查大小写和感叹号。

常见错误

不要漏掉端点,也不要把 && 写成 ||,否则范围外的数也可能被判为适合。输出中的小写字母与感叹号都要保留。

自测

输入:25 → 预期输出:ok!

输入:30 → 预期输出:ok!

输入:24 → 预期输出:no!

输入:30.5 → 预期输出:no!

自己试试:在温度分界点两边各选一个数。

这两次运行,应该进入同一个分支吗?

参考代码
#include <iostream>

using namespace std;

int main() {
    double t;
    cin >> t;

    if (t >= 25 && t <= 30) cout << "ok!";
    else cout << "no!";

    return 0;
}

2055:【例 3.5】收费

提示 1:先确定用哪种单价

20和21分别落在哪个收费档?

这里要看清楚,选好单价以后,全部都按这个单价算。

不是只算超出的那一点。

提示 2:把比较和计算放在同一分支

读入double x后,第一档:

if (x <= 20) printf("%.2lf", x * 1.68);

补上另一档,选好单价后才计算,别把两档费用累加。

常见错误

不要写成 20 * 1.68 + (x - 20) * 1.98,那是另一种收费规则。拿 \(20\) 与 \(21\) 分别检查单价。

自测

输入:20.00 → 预期输出:33.60

输入:21.00 → 预期输出:41.58

输入:1.50 → 预期输出:2.52

参考代码
#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    double x;
    cin >> x;

    if (x <= 20) printf("%.2lf", x * 1.68);
    else printf("%.2lf", x * 1.98);

    return 0;
}

2056:【例 3.7】最大的数

提示 1:让最大值待在同一位置

读入double x, y, z后,先让x和y比,谁大就把谁放进x。

提示 2:再让擂主比第三个数
if (x < y) swap(x, y);

仿照它让x再与z比较,最后只输出x;用全负数再试。

常见错误

最大值不要初始设为 \(0\),三个数都为负数时会出错。多个数相等时也必须正常输出。

自测

输入:1.01 2 3.22 → 预期输出:3.22

输入:-3.5 -1.2 -2 → 预期输出:-1.2

输入:2 2 2 → 预期输出:2

自己试试:构造一组全为负数的数据。

它能检查出“最大值初始设为零”的错误吗?

参考代码
#include <iostream>
#include <utility>

using namespace std;

int main() {
    double x, y, z;
    cin >> x >> y >> z;

    if (x < y) swap(x, y);
    if (x < z) swap(x, z);

    cout << x;

    return 0;
}

1039:判断数的正负

提示 1:零要单独留一条路

在数轴上标正数、0、负数,三种情况会不会重叠?

提示 2:前两种判断完,剩下是什么
if (n > 0) cout << "positive";
else if (n == 0) cout << "zero";

n已读入;自己补最后一个分支,别把赋值=当成比较==。

常见错误

不要漏掉 \(0\),也不要输出中文解释。分别用正数、零、负数检查三个分支。

自测

输入:1 → 预期输出:positive

输入:0 → 预期输出:zero

输入:-1000000000 → 预期输出:negative

参考代码
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n > 0) cout << "positive";
    else if (n == 0) cout << "zero";
    else cout << "negative";

    return 0;
}

1040:输出绝对值

提示 1:绝对值看离零多远

-3.14与3.14离零的距离相同,哪一种输入需要改变符号?

提示 2:只对负数取相反数

读入double a后:

if (a < 0) a = -a;

按两位小数输出;若出现-0.00,检查原输入是否为负零,代码中可把等于0的值重新赋成0。

常见错误

不要用 int 读入浮点数,也不要把所有输入都取相反数。\(0\) 的绝对值仍是 \(0\),必须输出 0.00。

自测

输入:-3.14 → 预期输出:3.14

输入:0 → 预期输出:0.00

输入:2.5 → 预期输出:2.50

输入:-0.00 → 预期输出:0.00

自己试试:自己选一对相反数,先写出各自的预期输出。

这两次输出应该有什么关系?

方法一:分支取相反数
#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    double a;
    cin >> a;

    if (a < 0) a = -a;
    if (a == 0) a = 0;  // 让零按 0.00 输出

    printf("%.2lf", a);

    return 0;
}
方法二:fabs 函数
#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;

int main() {
    double a;
    cin >> a;

    printf("%.2lf", fabs(a));

    return 0;
}

1041:奇偶数判断

提示 1:余数用来区分奇偶

1、2、100除以2分别余多少?

判断偶数只需检查余数是否为0。

提示 2:先写不会受符号影响的判断
if (n % 2 == 0) cout << "even";

n已读入,另一个分支输出odd;这种写法不依赖奇数余数一定是1。

常见错误

“余数只能是 \(0\) 或 \(1\)”依赖本题输入为正整数。若以后允许负数,C++ 的 -3 % 2 是 \(-1\),判断奇数应写 n % 2 != 0。

自测

输入:1 → 预期输出:odd

输入:2 → 预期输出:even

输入:100 → 预期输出:even

方法一:比较余数
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n % 2 != 0) cout << "odd";
    else cout << "even";

    return 0;
}
方法二:判断余数是否非零
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n % 2 != 0) cout << "odd";
    else cout << "even";

    return 0;
}
方法三:按位与(拓展)
// & 1 的结果如果非零,可以用来判断是奇数
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n & 1) cout << "odd";
    else cout << "even";

    return 0;
}

1042:奇偶 ASCII 值判断

提示 1:判断的是编码,不是字符长相

A的ASCII码为65,字符0的ASCII码为48,各是奇数还是偶数?

提示 2:读取字符,再看编码余数
char c;
cin.get(c);
int x = c;

用x % 2判断,奇数输出YES;cin.get也能读入空格,不会自动跳过它。

常见错误

不能先减去 '0',那是在把数字字符转换为对应数字,不是取 ASCII 值。输出是大写 YES、NO。

自测

输入:A → 预期输出:YES

输入:B → 预期输出:NO

输入:0 → 预期输出:NO

参考代码
#include <iostream>

using namespace std;

int main() {
    char c;
    cin.get(c);

    int x = c;
    if (x % 2 == 1) cout << "YES";
    else cout << "NO";

    return 0;
}

1043:整数大小比较

提示 1:比较前先确保读得下

输入可能超过普通32位int的上限,两个变量该用什么类型?

提示 2:三种关系只走一种

用long long x, y读入后:

if (x > y) cout << '>';
else if (x == y) cout << '=';

补第三种关系,输出符号本身,不是输出文字说明。

常见错误

不要把第一个数硬塞进 int。也不要一正一负地混用无符号与有符号类型,那样比较时的类型转换可能改变结果。用最大值和负数一起检查。

自测

输入:4294967295 -1 → 预期输出:>

输入:0 0 → 预期输出:=

输入:0 1 → 预期输出:<

输入:0 -2147483648 → 预期输出:>

自己试试:从题目范围内选一个 int 装不下的数,再配一个负数。

先判断大小,再检查程序能否正确读入。

参考代码
#include <iostream>

using namespace std;

int main() {
    long long x, y;
    cin >> x >> y;

    if (x > y) cout << '>';
    else if (x == y) cout << '=';
    else cout << '<';

    return 0;
}

1044:判断是否为两位数

提示 1:从最小两位数到最大两位数

本题范围里的两位数从10到99,9和100能不能通过?

提示 2:两个边界都要包含
if (n >= 10 && n <= 99) cout << 1;

读入n后补不满足时输出0;不要只检查一边。

常见错误

这是判断两位正整数,不需要自行增加负数的规则。拿 \(9,10,99,100\) 检查四个临界值。

自测

输入:9 → 预期输出:0

输入:10 → 预期输出:1

输入:99 → 预期输出:1

输入:100 → 预期输出:0

参考代码
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n >= 10 && n <= 99) cout << 1;
    else cout << 0;

    return 0;
}

1045:收集瓶盖赢大奖

提示 1:只够一种也能换

10个幸运瓶盖、0个鼓励瓶盖能换吗?

再看0个幸运、20个鼓励,这两种不必同时达标。

提示 2:达到任意一项即可

已读入两种数量a、b:

a >= 10 || b >= 20

把条件放进if,两种都够时也只输出一次1。

常见错误

不要把两类数量相加后比较,也不要用 && 强迫两类都达标。“达到”包含刚好够数的情况。

自测

输入:10 0 → 预期输出:1

输入:0 20 → 预期输出:1

输入:9 19 → 预期输出:0

输入:10 20 → 预期输出:1

自己试试:试着只改变一种瓶盖的数量,让结果从不能兑换变成能兑换。

另外一种数量保持不变,方便看出变化来自哪里。

参考代码
#include <iostream>

using namespace std;

int main() {
    int a, b;
    cin >> a >> b;

    if (a >= 10 || b >= 20) cout << 1;
    else cout << 0;

    return 0;
}

1046:判断一个数能否同时被 3 和 5 整除

提示 1:同时整除需要两个零余数

15符合,9只被3整除,能通过吗?

提示 2:用且连接完整条件
if (n % 3 == 0 && n % 5 == 0) cout << "YES";

n已读入,自己补NO分支;0和负数也按同一余数规则判断。

常见错误

不能写成 ||,只被其中一个数整除不符合题意。\(0\) 除以这两个非零数余数都为 \(0\),应输出 YES。

自测

输入:15 → 预期输出:YES

输入:9 → 预期输出:NO

输入:-30 → 预期输出:YES

输入:0 → 预期输出:YES

方法一:分别判断两个除数
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n % 3 == 0 && n % 5 == 0) cout << "YES";
    else cout << "NO";

    return 0;
}
方法二:判断是否被 15 整除
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n % 15 == 0) cout << "YES";
    else cout << "NO";

    return 0;
}

1047:判断能否被 3,5,7 整除

提示 1:先列哪些除数能留下

给15分别做除以3、5、7的余数检查,应该留下几个除数?

不能只找到一个就忽略其他条件。

提示 2:先写三个都满足的情况
if (n % 3 == 0 && n % 5 == 0 && n % 7 == 0)
    cout << "3 5 7";

沿方法一继续写恰好两个、恰好一个的分支;为什么三个都满足的要放前面?

提示 3:一个都没有时才输出n

把11代入自己写的分支,最后应走到else cout << 'n';。

别把找不到写成NO,也别把输出除数写成输出商。

常见错误

只写一串单独的 else if (n % 3 == 0)、else if (n % 5 == 0) 会漏掉同时成立的情况。输出的是除数,不是商;没有一个整除时是小写 n,不是 NO。

自测

输入:105 → 预期输出:3 5 7

输入:15 → 预期输出:3 5

输入:21 → 预期输出:3 7

输入:35 → 预期输出:5 7

输入:9 → 预期输出:3

输入:25 → 预期输出:5

输入:49 → 预期输出:7

输入:11 → 预期输出:n

自己试试:能构造一个只被 \(5\) 和 \(7\) 整除、不能被 \(3\) 整除的数吗?

先用取余核对,再写出预期输出。

方法一:列出整除组合
// 把所有情况列举一遍
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n % 3 == 0 && n % 5 == 0 && n % 7 == 0) cout << 3 << ' ' << 5 << ' ' << 7;
    else if (n % 3 == 0 && n % 5 == 0) cout << 3 << ' ' << 5;
    else if (n % 3 == 0 && n % 7 == 0) cout << 3 << ' ' << 7;
    else if (n % 5 == 0 && n % 7 == 0) cout << 5 << ' ' << 7;
    else if (n % 3 == 0) cout << 3;
    else if (n % 5 == 0) cout << 5;
    else if (n % 7 == 0) cout << 7;
    else cout << 'n';

    return 0;
}
方法二:分别判断并控制空格
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    bool flag = false;
    if (n % 3 == 0) {
        cout << 3;
        flag = true;
    }
    if (n % 5 == 0) {
        if (flag) cout << ' ';
        cout << 5;
        flag = true;
    }
    if (n % 7 == 0) {
        if (flag) cout << ' ';
        cout << 7;
        flag = true;
    }

    if (!flag) cout << 'n';

    return 0;
}

1048:有一门课不及格的学生

提示 1:恰好一门和至少一门不同

把两科及格、不及格的四种搭配画出来,哪两种需要输出1?

提示 2:先写其中一种搭配

两科存入a、b:

a < 60 && b >= 60

这是第一科不及格、第二科及格;交换两者补另一种,用||连接后放入if。

常见错误

刚好 \(60\) 分算及格。只判断 a < 60 || b < 60,会把两门都不及格也算进去。

自测

输入:59 60 → 预期输出:1

输入:60 59 → 预期输出:1

输入:59 59 → 预期输出:0

输入:60 60 → 预期输出:0

自己试试:先列出两门成绩“及格、不及格”的所有搭配。

自己为每种搭配选一组分数,看看有没有漏掉的情况。

方法一:列出两种情况
#include <iostream>

using namespace std;

int main() {
    int a, b;
    cin >> a >> b;

    if ((a < 60 && b >= 60) || (a >= 60 && b < 60)) cout << 1;
    else cout << 0;

    return 0;
}
方法二:比较两个判断结果
#include <iostream>

using namespace std;

int main() {
    int a, b;
    cin >> a >> b;

    if ((a < 60) != (b < 60)) cout << 1;
    else cout << 0;

    return 0;
}

第二节 switch语句

2057:【例 3.9】星期几

提示 1:一个数字只选一个星期

输入1时,输出Monday后还要继续执行星期二吗?

提示 2:先写一个case

读入整数n后,在switch (n)里写:

case 1: cout << "Monday"; break;

补2到7以及default;缺少break会在哪种输入上暴露问题?

常见错误

漏写 break,程序可能继续执行后面的分支。星期名称首字母大写;错误信息中的空格、感叹号也要一致。

自测

输入:1 → 预期输出:Monday

输入:7 → 预期输出:Sunday

输入:0 → 预期输出:input error!

输入:8 → 预期输出:input error!

参考代码
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    switch (n) {
        case 1: cout << "Monday"; break;
        case 2: cout << "Tuesday"; break;
        case 3: cout << "Wednesday"; break;
        case 4: cout << "Thursday"; break;
        case 5: cout << "Friday"; break;
        case 6: cout << "Saturday"; break;
        case 7: cout << "Sunday"; break;

        default: cout << "input error!";
    }

    return 0;
}

2058:【例 3.10】简单计算器

提示 1:先看输入顺序和数的类型

2 1.2 -是两个数在前、符号在后,变量不能都用int。

提示 2:先完成减法分支

double a, b和char c已按顺序读入,放进switch (c):

case '-': cout << a - b; break;

补加、乘;除法分支先看b是否为0,无效符号交给default。

提示 3:把两种错误分开测

1 0 /是除零,1 0 %是无效符号。

不要在判断符号前统一检查b == 0;错误文字按题面复制核对。

常见错误

不能把这题与 1057 的整数除法混在一起:本题 7 2 / 应得到 \(3.5\)。错误运算符即使配上第二个数 \(0\),也应输出无效符号,不能先统一判除零。

自测

输入:2 1.2 - → 预期输出:0.8

输入:7 2 / → 预期输出:3.5

输入:1 0 / → 预期输出:Divided by zero!

输入:1 0 % → 预期输出:Invalid operator!

输入:3 4 * → 预期输出:12

自己试试:自己补一组还没测过的运算。

再选一个无效符号,想想它应该触发哪个分支。

方法一:switch 选择运算
#include <iostream>

using namespace std;

int main() {
    double a, b;
    char c;
    cin >> a >> b >> c;

    switch (c) {
        case '+': cout << a + b; break;
        case '-': cout << a - b; break;
        case '*': cout << a * b; break;
        case '/':
            if (b == 0) cout << "Divided by zero!";
            else cout << a / b;
            break;
        default: cout << "Invalid operator!";
    }

    return 0;
}
方法二:if 选择运算
#include <iostream>

using namespace std;

double a, b;
char c;

int main() {
    cin >> a >> b >> c;
    if (c == '+') cout << a + b;
    else if (c == '-') cout << a - b;
    else if (c == '*') cout << a * b;
    else if (c == '/') {
        if (b == 0) cout << "Divided by zero!";
        else cout << a / b;
    } else cout << "Invalid operator!";

    return 0;
}

2059:【例 3.11】买笔

提示 1:先买最便宜的,再考虑换笔

每支至少4元,金额x最多能买x / 4支。

先用11元手算:两支4元笔后还剩3元,怎样换成贵一点的笔把钱用完?

提示 2:余数决定需要补多少钱
long long n = x / 4, r = x % 4;

x已读入;余1把一支4元笔换成5元,余2换成6元,余3需要换几支?

提示 3:输出前核对数量和总价

用a、b、c分别记6元、5元、4元笔的数量。

先试r = 0,再补三个余数分支;别把无解金额擅自规定成输出-1。

知识补充

例如 \(11=4\times2+3\),先设想买两支 \(4\) 元笔,再分别补 \(1\) 元、\(2\) 元,变成一支 \(5\) 元笔和一支 \(6\) 元笔,仍是两支,总价正好 \(11\) 元。这里没有规定最优方案必须唯一,例如 \(10\) 元也可以用两支 \(5\) 元笔花完。

常见错误

余 \(3\) 元时需要替换两支笔,不能只减掉一支,也不能产生负的购买数量。金额 \(1,2,3,7\) 本来就无法恰好花完,题面未规定这些无解金额的输出,不能自行添加 -1 等规则;参考代码在这些金额下不输出方案。

自测

输入:4 → 预期输出:0 0 1

输入:5 → 预期输出:0 1 0

输入:6 → 预期输出:1 0 0

输入:10 → 预期输出:1 0 1

输入:11 → 预期输出:1 1 0

输入:12 → 预期输出:0 0 3

自己试试:自己选一个能恰好花完的金额,先手算一种方案。

除了核对总价,还能怎样确认笔的支数已经最多?

方法一:按余数分类
#include <iostream>

using namespace std;

int main() {
    long long x;
    cin >> x;

    long long n = x / 4, r = x % 4;
    long long a = 0, b = 0, c = n;
    if (r == 1) {
        if (n < 1) {

            return 0;
        }
        b = 1;
        c--;
    } else if (r == 2) {
        if (n < 1) {

            return 0;
        }
        a = 1;
        c--;
    } else if (r == 3) {
        if (n < 2) {

            return 0;
        }
        a = b = 1;
        c -= 2;
    }

    cout << a << ' ' << b << ' ' << c;

    return 0;
}
方法二:枚举购买数量(拓展)
#include <iostream>

using namespace std;

int main() {
    long long x;
    cin >> x;

    for (long long c = x / 4; c >= 0; c--)
        for (long long b = (x - 4 * c) / 5; b >= 0; b--) {
            long long rest = x - 4 * c - 5 * b;
            if (rest % 6 == 0) {
                cout << rest / 6 << ' ' << b << ' ' << c;

                return 0;
            }
        }

    return 0;
}

1049:晶晶赴约会

提示 1:题目问能赴约,不是有没有课

周一、三、五有课,输入3时该答YES还是NO?

提示 2:每种相等都要写完整
if (n == 1 || n == 3 || n == 5) cout << "NO";

n已读入,剩下补YES;n == 1 || 3 || 5为什么不是三个相等判断?

常见错误

不要把有课与能赴约写反。每个等号比较都必须完整,例如不能写成 n == 1 || 3 || 5。

自测

输入:1 → 预期输出:NO

输入:3 → 预期输出:NO

输入:5 → 预期输出:NO

输入:2 → 预期输出:YES

输入:7 → 预期输出:YES

参考代码
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n == 1 || n == 3 || n == 5) cout << "NO";
    else cout << "YES";

    return 0;
}

1050:骑车与走路

提示 1:先把准备时间加进去

骑车比走路快,但取车、停车还要50秒。

先别急着比较,取100米,两种走法各算一次时间。

提示 2:一起乘6,避免浮点误差

距离存入dist,两种时间同时乘6:

int walk = 5 * dist;
int bike = 300 + 2 * dist;

比较较小值决定输出,最后还要留哪个相等分支?

常见错误

不要漏掉固定的 \(50\) 秒。直接比较两个浮点除法的结果,可能把本应相等的 \(100\) 米误判为不同;不要凭感觉加一个误差值,先看能否转成准确的整数比较。

自测

输入:99 → 预期输出:Walk

输入:100 → 预期输出:All

输入:101 → 预期输出:Bike

自己试试:用题目给的速度,自己算出两种方式一样快的距离。

再在它两边各选一个距离,先预测结果。

方法一:比较放大后的总时间
#include <iostream>

using namespace std;

int main() {
    int dist;
    cin >> dist;

    int walk = 5 * dist;
    int bike = 300 + 2 * dist;

    if (walk < bike) cout << "Walk";
    else if (bike < walk) cout << "Bike";
    else cout << "All";

    return 0;
}
方法二:按距离分界点判断
#include <iostream>

using namespace std;

int main() {
    int dist;
    cin >> dist;

    if (dist < 100) cout << "Walk";
    else if (dist > 100) cout << "Bike";
    else cout << "All";

    return 0;
}

1051:分段函数

提示 1:先选公式,再代数字

把0、5、10、19分别放到题面的三个区间中,5和10归哪一段?

提示 2:先完成第一段

读入double x,结果用double y:

if (x >= 0 && x < 5) y = -x + 2.5;

补其余分支;第二段的平方必须写(x - 3) * (x - 3),不能用^。

提示 3:边界值比中间值更能查错

拿5、10各手算一次,先确认选对公式,再核对保留三位小数的输出。

常见错误

C++ 中 ^ 不能表示平方,x - 3 * x - 3 也不等于 \((x-3)^2\)。先选分支,再代入公式;拿分界点手算,而不是只测每一段中间的数。

自测

输入:0 → 预期输出:2.500

输入:5 → 预期输出:-4.000

输入:10 → 预期输出:3.500

输入:19 → 预期输出:8.000

自己试试:为每一段各选一个输入,再检查分界点。

你选的数据能区分“公式算错”和“分支选错”吗?

参考代码
#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    double x;
    cin >> x;

    double y;
    if (x >= 0 && x < 5) y = -x + 2.5;
    else if (x >= 5 && x < 10) y = 2 - 1.5 * (x - 3) * (x - 3);
    else y = x / 2 - 1.5;

    printf("%.3lf", y);

    return 0;
}

1052:计算邮资

提示 1:先看刚好多出1克

1000克8元,1001克已经多出一份超重费。

再拿1500克、1501克算一下,就多1克,费用变不变?

提示 2:超出的重量单独算

只有n > 1000时,才进入这个分支:

int w = n - 1000;
int k = (w + 499) / 500;

w是超出的克数,k是计费份数;把k * 4加到原来的8元上。

提示 3:加急费再单独处理

加急标志为y时,再加5元。

超重又加急,两笔都要收,所以加急判断不能写成前一个判断的else if。

常见错误

不要直接用 w / 500,那样 \(1\) 到 \(499\) 克会被算成零份。加急与重量是两件事,不要写成互斥的 else if。

自测

输入:1000 n → 预期输出:8

输入:1001 n → 预期输出:12

输入:1500 n → 预期输出:12

输入:1501 y → 预期输出:21

自己试试:自己找一个邮资刚好要增加的位置,选它前后相差 \(1\) 克的两个重量。

重量不变,只改加急标志,费用又应相差多少?

参考代码
#include <iostream>

using namespace std;

int n, cost;
char y;

int main() {
    cin >> n >> y;
    cost = 8;
    if (n > 1000) {
        int w = n - 1000;
        int k = (w + 499) / 500;
        cost += k * 4;
    }
    if (y == 'y') cost += 5;

    cout << cost;

    return 0;
}

1053:最大数输出

提示 1:第一个输入就能当擂主

对-3 -1 -2,若把最大值先设成0,会输出哪个没出现过的数?

提示 2:后来的挑战当前最大值

已读入a、b、c:

int ans = a;
if (b > ans) ans = b;

自己补c的比较;这里不用另外猜一个很小很小的数。

常见错误

不要把最大值初始设成 \(0\),全是负数时就会出错。可以先让ans = a,再分别和另外两个数比较。

自测

输入:-3 -1 -2 → 预期输出:-1

输入:5 5 5 → 预期输出:5

输入:9 2 3 → 预期输出:9

方法一:分支维护最大值
#include <iostream>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    int ans = a;
    if (b > ans) ans = b;
    if (c > ans) ans = c;

    cout << ans;

    return 0;
}
方法二:嵌套调用 max
#include <iostream>
#include <algorithm>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    cout << max(max(a, b), c);

    return 0;
}
方法三:用列表调用 max
#include <iostream>
#include <algorithm>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    cout << max({a, b, c});

    return 0;
}

1054:三角形判断

提示 1:等于两边之和会被拉直

先画边长1、2、3,三条边能围出面积吗?

需要严格大于,不是大于等于。

提示 2:三组比较同时成立

读入a、b、c:

a + b > c && a + c > b && b + c > a

放进if后输出yes或no;用10、2、3检查是否漏了一组比较。

常见错误

必须是严格大于,等于时三点在同一直线上。未先确定最长边时,只检查 a + b > c 不够,例如 \(10,2,3\) 会暴露这个问题。

自测

输入:3 4 5 → 预期输出:yes

输入:1 2 3 → 预期输出:no

输入:10 2 3 → 预期输出:no

自己试试:自己找三条能组成三角形的边,再只改一条,让两边之和恰好等于第三边。

把最长边换到不同输入位置,结果应该改变吗?

参考代码
#include <iostream>

using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b >> c;

    if (a + b > c && b + c > a && a + c > b) cout << "yes";
    else cout << "no";

    return 0;
}

1055:判断闰年

提示 1:把整百年单独挑出来

2024、1900、2000都能被4整除,哪两个是闰年?

提示 2:先写普通闰年的条件

年份存入n:

n % 4 == 0 && n % 100 != 0

再用||接上能被400整除的情况;用1900与2000检查两个例外。

常见错误

只判断 % 4 == 0 会把 \(1900\) 判错;只排除整百年又会把 \(2000\) 判错。注意“不能被 \(100\) 整除”要写 % 100 != 0。

自测

输入:2024 → 预期输出:Y

输入:1900 → 预期输出:N

输入:2000 → 预期输出:Y

输入:2023 → 预期输出:N

自己试试:自己找一个能被 \(4\) 整除的整百年份。

还需要检查什么,才能写下预期结果?

方法一:组合逻辑条件
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;

    if ((n % 4 == 0 && n % 100 != 0) || (n % 400 == 0)) cout << 'Y';
    else cout << 'N';

    return 0;
}
方法二:依次判断整除关系
#include <iostream>

using namespace std;

int main() {
    int year;
    cin >> year;

    if (year % 400 == 0) cout << 'Y';
    else if (year % 100 == 0) cout << 'N';
    else if (year % 4 == 0) cout << 'Y';
    else cout << 'N';

    return 0;
}

1056:点和正方形的关系

提示 1:横纵坐标要分别过关

画出横纵坐标都从-1到1的正方形,边界上的点也算。

点(2,0)只满足纵坐标范围,能算里面吗?

提示 2:先写横坐标,再补纵坐标
x >= -1 && x <= 1

两个整数坐标已读入x、y;仿照写纵坐标条件,用&&连接,四个比较缺一不可。

常见错误

只判断横坐标或者只判断纵坐标,都可能把外面的点算进来。不要写严格不等号漏掉边与顶点,也不要写成 C++ 的连续比较。

自测

输入:0 0 → 预期输出:yes

输入:1 -1 → 预期输出:yes

输入:2 0 → 预期输出:no

输入:0 -2 → 预期输出:no

自己试试:分别构造内部、边界、外部的点。

能否只改变一个坐标,让点从边界移到外部?

参考代码
#include <iostream>

using namespace std;

int main() {
    int x, y;
    cin >> x >> y;

    if (x >= -1 && x <= 1 && y >= -1 && y <= 1) cout << "yes";
    else cout << "no";

    return 0;
}

1057:简单计算器

提示 1:这次做整数除法

输入7 2 /与2058的小数计算器有什么不同?

先读两个整数a、b,再读符号c。

提示 2:除法分支先看除数
if (b == 0) cout << "Divided by zero!";
else cout << a / b;

这段只放在c == '/'的分支,其他运算分别处理;无效符号输出题目规定的文字。

提示 3:负商向零截断

先手算-7 / 2的C++结果,再运行检查。

不要为了让输出像数学小数而改成double。

常见错误

不要照搬 2058,把变量都改成 double,这样会改变本题的整数除法。负数时也不是向下取整,例如 -7 / 2 为 \(-3\)。

自测

输入:7 2 / → 预期输出:3

输入:-7 2 / → 预期输出:-3

输入:1 0 / → 预期输出:Divided by zero!

输入:1 0 % → 预期输出:Invalid operator!

输入:-10000 10000 * → 预期输出:-100000000

自己试试:自己选两个不能整除的整数,先写出商。

再只改变一个数的正负号,检查你是否分清了向零截断。

参考代码
// 如果if下面只有一句话,可以不用大括号,也可以提上来写
// 输出的话,一定要复制粘贴,千万不要自己敲,你看不清那些大小写和空格的
#include <iostream>

using namespace std;

int main() {
    int a, b;
    char c;
    cin >> a >> b >> c;

    if (c == '+') cout << a + b;
    else if (c == '-') cout << a - b;
    else if (c == '*') cout << a * b;
    else if (c == '/') {
        if (b == 0) cout << "Divided by zero!";
        else cout << a / b;
    } else cout << "Invalid operator!";

    return 0;
}

1058:求一元二次方程

提示 1:先决定能不能开平方

读入double a, b, c,先求根号内的数:

double delta = b * b - 4 * a * c;

用1 -3 2算得1,用1 0 1算得-4;负数时先走“没有实根”的分支。

提示 2:有实根时,先写一根

delta等于0时,两根相同;大于0时,分别计算两根。

double x1 = (-b - sqrt(delta)) / (2 * a);

把减号换成加号,补出另一根;分母的2 * a要一起括起来。

提示 3:最后才排顺序和输出

不要认定带减号的一根一定小,a为负时顺序可能反过来。

两根先算出来,比一下大小。

最后的等号、分号,还有五位小数,照题目写,别漏了。

知识补充

浮点计算可能有舍入误差,不能把任意一个固定的 eps 当成所有题目通用的“相等标准”。这里按题面给出的三种判别式情况实现;若只在非常接近重根时出错,把具体系数和算出的判别式一起检查。

常见错误

分母是整个 2 * a,必须写成 ... / (2 * a)。不要在判断判别式之前开平方,也不要自行为 \(a=0\) 设计一次方程分支,题面已经排除它。调试时先写出判别式和两根的手算值,再带着出错输入、实际输出找老师一起看。

自测

输入:1 -3 2 → 预期输出:x1=1.00000;x2=2.00000

输入:1 -2 1 → 预期输出:x1=x2=1.00000

输入:1 0 1 → 预期输出:No answer!

输入:-1 3 -2 → 预期输出:x1=1.00000;x2=2.00000

输入:-15.97 19.69 12.02 → 预期输出:x1=-0.44781;x2=1.68075

输入:1 0 0 → 预期输出:x1=x2=0.00000

自己试试:可以反过来造数据:先选两个整数根,再展开 \((x-r)(x-s)=0\) 得到系数。

试着让两根相等,或把三个系数同时乘以 \(-1\),预期结果应怎样变化?

参考代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <utility>

using namespace std;

int main() {
    double a, b, c;
    cin >> a >> b >> c;

    double delta = b * b - 4 * a * c;
    if (delta < 0) cout << "No answer!";
    else if (delta == 0) {
        double x = -b / (2 * a);
        if (x == 0) x = 0;
        printf("x1=x2=%.5lf", x);
    } else {
        double x1 = (-b - sqrt(delta)) / (2 * a);
        double x2 = (-b + sqrt(delta)) / (2 * a);
        if (x1 > x2) swap(x1, x2);
        if (x1 == 0) x1 = 0;
        if (x2 == 0) x2 = 0;
        printf("x1=%.5lf;x2=%.5lf", x1, x2);
    }

    return 0;
}