1.3 循环结构 参考代码

P5722 [深基4.例11] 数列求和

  1. 初始化一个变量:声明一个变量 sum 并将其初始值设为 0,这个变量将用来存储累加的和。
  2. 循环累加:使用一个 for 循环,从 1 开始,一直迭代到 n
  3. 累加操作:在循环的每一次迭代中,将当前的循环变量 i 的值加到 sum 上。
  4. 输出操作:循环结束后,sum 变量中就存储了 1n 的总和,将 sum 的值输出即可。
参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    int sum = 0; // 存储累加和
    for (int i = 1; i <= n; i++) { // 使用 for 循环,从 1 迭代到 n
        sum += i; // 将当前的循环变量 i 的值累加到 sum 中
    }
    // 循环结束后,sum 中存储了 1 到 n 的总和
    printf("%d\n", sum);
    return 0;
}

P5718 [深基4.例2] 找最小值

参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    int mn = 1001; // 存储最小值,并初始化为一个比任何可能的输入值都大的数
    for (int i = 0; i < n; ++i) { // 使用 for 循环,迭代 n 次
        int x; // 存储当前输入的整数
        scanf("%d", &x);
        if (x < mn) { // 如果当前输入的整数 x 小于当前的最小值 mn
            mn = x; // 更新 mn 的值为 x
        } 
    }
    // 循环结束后,mn 中存储的就是所有输入整数中的最小值
    printf("%d\n", mn);
    return 0;
}

P5724 【深基4.习5】求极差 / 最大跨度值 / 最大值和最小值的差

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int n;
    scanf("%d", &n);
    // 初始化最大值 mx 和最小值 mn
    // 根据题目提示,a_i 的范围是 [0, 1000]
    // mx 初始化为 0,任何范围内的数都会大于等于它
    // mn 初始化为 1000,任何范围内的数都会小于等于它
    int mx = 0, mn = 1000;
    for (int i = 0; i < n; ++i) { // 循环 n 次,来读取和处理每一个输入的整数
        int x;
        scanf("%d", &x); // 读取当前循环中的整数
        mx = max(mx, x); // 使用 max 函数,比较当前的 mx 和新读入的 x,将较大的值赋给 mx
        mn = min(mn, x); // 使用 min 函数,比较当前的 mn 和新读入的 x,将较小的值赋给 mn
    }
    // 循环结束后,mx 就是所有数字中的最大值,mn 就是最小值
    printf("%d\n", mx - mn);
    return 0;
}

P1085 [NOIP2004 普及组] 不高兴的津津

参考代码
#include <cstdio>
int main()
{
    int unhappy = 8;// 用于记录最不高兴的程度,即最长的上课时间,初始值设为 8,因为只有超过8小时才算不高兴
    int day = 0;    // 用于记录最不高兴的是星期几,初始值为0
    for (int i = 1; i <= 7; i++) { // 循环7次,代表一周的7天
        int a, b; // 分别存储学校上课时间和课外上课时间
        scanf("%d%d", &a, &b);
        if (a + b > unhappy) { // 如果当天的总上课时间比之前最长的还长
            unhappy = a + b; // 更新最不高兴的程度
            day = i; // 记录下今天是星期几
        }
    }
    // 输出最不高兴的是星期几
    // 如果一周都没有不高兴,day 的值将保持为初始值 0
    printf("%d\n", day);
    return 0;
}

选择题:假设一个长度为 \(n\) 的整数数组中每个元素值互不相同,且这个数组是无序的。要找到这个数组中最大元素的时间复杂度是多少?

  • A. \(O(n)\)
  • B. \(O(\log n)\)
  • C. \(O(n \log n)\)
  • D. \(O(1)\)
答案

A


选择题:以比较为基本运算,在 n 个数的数组中找最大的数,在最坏情况下至少要做多少次运算?

  • A. n/2
  • B. n-1
  • C. n
  • D. n+1
答案

B


选择题:以比较为基本运算,对于 2n 个数,同时找到最大值和最小值,最坏情况下需要的最小的比较次数为?

  • A. 4n-2
  • B. 3n+1
  • C. 3n-2
  • D. 2n+1
答案

C

首先将 2n 个数两两分组,共 n 组。对每一组进行 1 次比较,得到 n 个较大值和 n 个较小值。这个步骤需要 n 次比较。

真正的最大值一定存在于那 n 个较大值中。从这 n 个较大值里找到最大值,需要 n-1 次比较。

真正的最小值一定存在于那 n 个较小值中。从这 n 个较小值里找到最小值,需要 n-1 次比较。

总共需要的比较次数为 n+(n-1)+(n-1)=3n-2。


P5719 [深基4.例3] 分类平均

解题思路

要计算平均数,需要两个关键信息:总和个数。因此,需要为 A 类和 B 类数分别维护这两个值。

参考代码
#include <cstdio>
int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    int sum1 = 0, cnt1 = 0; // A 类数(能被 k 整除)的总和与个数
    int sum2 = 0, cnt2 = 0; // B 类数(不能被 k 整除)的总和与个数
    for (int i = 1; i <= n; ++i) { // 使用 for 循环,遍历从 1 到 n 的所有整数
        if (i % k == 0) { // 使用取模运算符判断 i 是否能被 k 整除
            // 如果是,则为 A 类数
            sum1 += i; // 将 i 累加到 A 类的总和中
            ++cnt1; // A 类的个数加 1
        } else {
            // 如果不是,则为 B 类数
            sum2 += i; // 将 i 累加到 B 类的总和中
            ++cnt2; // B 类的个数加 1
        }
    }
    // 计算并输出结果
    // sum1 * 1.0 将计算结果转换为浮点数,以确保执行的是浮点数除法
    // %.1f 格式化输出,表示保留一位小数的浮点数
    printf("%.1f %.1f\n", sum1 * 1.0 / cnt1, sum2 * 1.0 / cnt2);
    return 0;
}

P1075 [NOIP2012 普及组] 质因数分解

题目给出了一个关键信息:正整数 \(n\)两个不同质数的乘积。假设这两个质数是 \(p_1\)\(p_2\),且 \(p_1 \lt p_2\),那么 \(n = p_1 \times p_2\),目标是找到较大的那个质数,也就是 \(p_2\)

根据这个前提,解题思路可以简化为:只需要找到 \(n\) 的两个质因数中较小的那一个 \(p_1\),一旦找到了 \(p_1\),就可以通过 \(n / p_1\) 计算出较大的质因数 \(p_2\)

那么,如何高效地找到最小的质因数 \(p_1\) 呢?

可以从最小的质数 2 开始,依次向上尝试,看哪个数能整除 \(n\)。由于是从小到大依次尝试的,所以第一个找到的能整除 \(n\) 的数,必然是 \(n\) 的最小质因数。一个重要的优化:不需要一直尝试到 \(n\)。对于一个合数 \(n\),它的最小质因数一定不会超过 \(\sqrt{n}\)。因此,循环只需要进行到 \(\sqrt{n}\) 即可。代码中可以使用 i * i <= n 来判断,避免平方根的计算,效率更高。

所以,完整的算法是:从 \(2\) 开始循环到 \(\sqrt{n}\),找到第一个能整除 \(n\) 的数 \(i\),这个 \(i\) 就是较小的质因数,然后输出 \(n/i\) 即可。

参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    // 循环寻找 n 的最小质因数
    // i 从 2 开始,因为 2 是最小的质数
    // 循环条件 i * i <= n 是一个重要的优化,一个合数的最小质因数必然小于或等于它的平方根
    // 这样可以大大减少循环次数,提高效率,避免超时
    for (int i = 2; i * i <= n; ++i)
        // 判断 i 是否能整除 n
        // 如果能整除,说明 i 是 n 的一个因数
        // 因为 i 是从 2 开始递增的,所以找到的第一个因数 i,必然是 n 的最小质因数
        if (n % i == 0) {
            // 题目已知 n 是两个不同质数的乘积
            // 如果 i 是较小的那个质数,那么 n / i 就是较大的那个质数
            printf("%d\n", n / i);
            break; // 已经找到了答案,所以用 break 立即跳出循环,结束程序
        }
    return 0;
}

P5720 [深基4.例4] 一尺之棰

参考代码
#include <cstdio>
int main()
{
    int a; // 木棍的初始长度
    scanf("%d", &a);
    int cur = 1; // 天数计数器,第一天木棍的长度就是 a
    // 使用 while 循环来模拟“日取其半”的过程
    // 循环的条件是木棍长度 a 仍然大于 1
    while (a > 1) {
        a /= 2; // 将木棍长度除以 2,整数除法自动向下取整
        ++cur; // 天数加一
    }
    // 当循环结束时,a 的长度变为 1,cur 中存储的就是对应的天数
    printf("%d\n", cur);
    return 0;
}

P1035 [NOIP2002 普及组] 级数求和

参考代码
#include <cstdio>
int main()
{
    int k;
    scanf("%d", &k);
    double s = 0; // 存储级数的和,用浮点数才能精确计算
    int n = 0; // 用于计数,表示当前加到了第几项
    // 使用 while 循环进行累加
    // 循环的条件是当前的和 s 小于或等于 k
    // 一旦 s > k,循环就会停止
    while (s <= k) {
        n++; // 项数加 1,从 n = 1 开始
        // 将第 n 项(1/n)的值加到总和 s 中
        // 使用 1.0 而不是 1 是为了确保进行的是浮点数除法,而不是整数除法
        s += 1.0 / n;
    }
    // 当循环结束时,n 就是使 s > k 成立的最小项数
    printf("%d\n", n);
    return 0;
}

P2669 [NOIP2015 普及组] 金币

参考代码
#include <cstdio>
int main()
{
    int k; // k 表示总共的天数
    scanf("%d", &k);
    int sum = 0; // 用于累加金币总数
    int coin = 1; // 当前这一天应该获得的金币数,初始为 1
    int day = 0; // 记录当前 coin 值已经持续了多少天
    for (int i = 1; i <= k; i++) { // 循环 k 次,模拟从第 1 天到第 k 天
        sum += coin; // 将当前的金币数累加到总数中
        day++; // 持续天数加 1
        // 如果持续天数等于当前每天的金币数
        // 例如:当 coin=1 时,day=1,表示第一天结束
        // 当 coin=2 时,day=2,表示连续两天获得 2 金币的周期结束
        if (day == coin) { 
            coin++; // 增加下一天要发的金币数
            day = 0; // 重置持续天数计数器
        }
    }
    printf("%d\n", sum);
    return 0;
}

P1307 [NOIP2011 普及组] 数字反转

参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    if (n < 0) { // 如果 n 是负数
        printf("-"); // 先输出负号
        n = -n; // 然后将 n 变为其相反数(正数),以便后续统一处理
    }
    int ans = 0; // 用于存储反转后的新数,初始化为 0
    // 循环直到 n 的所有位都被处理完
    // 当 n 变为 0 时,表示所有位都已经被取出
    while (n > 0) {
        // 取出 n 的最低位(个位)
        // 将取出的位附加到 ans 的末尾,ans * 10 是为了给新数字腾出位置
        ans = ans * 10 + n % 10;
        n /= 10; // 从 n 中移除已经处理过的最低位
    }
    // 此时 ans 中就是反转后的数字
    // 这个逻辑也正确处理了 n=0 的情况,因为循环不会执行,会直接输出 ans 的初始值 0
    printf("%d\n", ans);
    return 0;
}

P5721 [深基4.例6] 数字直角三角形

这是一个直角三角形,第一行有 \(n\) 个数字,第二行有 \(n-1\) 个,……,第 \(n\) 行有 \(1\) 个数字。三角形中的数字是连续递增的,从 01 开始。每个数字都占两个字符的宽度,不足两位的前面补 0。例如,1 显示为 01,9 显示为 09,10 显示为 10。

基于以上分析,可以使用嵌套循环来生成这个三角形。外层循环控制行数,从第 1 行到第 n 行。内层循环控制每行输出的数字个数,第 i 行(i 从 1 开始)需要输出 n - i + 1 个数字。可以用一个单独的计数器变量,每次输出一个数字后就自增 1。在输出每个数字时,可以使用 printf 的格式化字符串 "%02d" 来确保输出的数字始终是两位,不足的用 0 填充。每输出完一行数字后,需要输出一个换行符 \n

参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    int num = 0; // 初始化数字计数器为 0
    for (int i = 1; i <= n; i++) { // 外层循环控制行数,从 1 到 n
        // 内层循环控制每行输出的数字个数
        // 第 i 行需要输出 n - i + 1 个数字
        for (int j = 1; j <= n - i + 1; j++) {
            num++; // 数字计数器加 1
            printf("%02d", num);
        }
        printf("\n"); // 每行输出完毕后,输出一个换行符
    }
    return 0;
}

习题:P5725 [深基4.习8] 求三角形

参考代码
#include <cstdio>
int main()
{
    int n; scanf("%d", &n);
    // 1. 打印 n×n 的数字方阵
    int num = 0; // 初始化数字计数器
    for (int i = 1; i <= n; i++) { // 外层循环控制行数,从 1 到 n
        for (int j = 1; j <= n; j++) { // 内层循环控制每行输出的数字个数,从 1 到 n
            num++; // 数字加 1
            printf("%02d", num); // 使用 %02d 格式化输出,确保数字占两位,不足则前面补 0
        }
        printf("\n"); // 每行输出完毕后,换行
    }
    // 2. 在两个图形之间打印一个空行
    printf("\n");
    // 3. 打印数字三角形
    num = 0; // 重置数字计数器
    for (int i = 1; i <= n; i++) { // 外层循环控制行数,从 1 到 n
        // 内层循环,用于打印前导空格,实现右对齐
        // 第 i 行需要打印 n - i 个空格对(每个空格对占两个字符宽度)
        for (int j = 1; j <= n - i; j++) {
            printf("  "); // 输出两个空格
        }
        // 内层循环,用于打印数字
        // 第 i 行需要打印 i 个数字
        for (int j = 1; j <= i; j++) {
            num++; // 数字加 1
            printf("%02d", num); // 格式化输出数字
        }
        printf("\n"); // 每次输出完毕后,换行
    }
    return 0;
}

习题:P1980 [NOIP2013 普及组] 计数问题

参考代码
#include <cstdio>
int main()
{
    int n, x;
    scanf("%d%d", &n, &x);
    int ans = 0; // 初始化计数器 ans 为 0,用于记录 x 出现的总次数
    for (int i = 1; i <= n; i++) { // 外层循环:遍历从 1 到 n 的每一个整数 i
        // 将当前的整数 i 赋值给一个临时变量 t
        // 这样做是为了在内层循环中修改 t,而不影响外层循环的变量 i
        int t = i;
        // 内层循环:分解整数 t,检查它的每一位
        // 循环条件是 t > 0,当 t 变为 0 时,说明所有位都已被检查
        while (t > 0) {
            // 使用取模运算 t % 10 获取 t 的最低位(个位)
            // 判断这一位是否等于目标数字 x
            if (t % 10 == x) ans++; // 如果相等,计数器加 1
            // 使用整除运算 t /= 10 将 t 的最低位移除
            // 这样,在下一次循环中,原来的十位就变成了新的最低位
            t /= 10;
        }
    }
    printf("%d\n", ans);
    return 0;
}

P4956 [COCI2017-2018#6] Davor

首先,需要计算出在一周内能存多少钱。周一到周日,他分别存入:\(x, x+k, x+2k, x+3k, x+4k, x+5k, x+6k\)。将它们相加,得到一周的总存款:\(7x+21k\)。整个存款周期是 52 周,所以 52 周的总存款就是 \(364x+1092k\)

现在有了一个二元一次方程 \(364x+1092k=n\),需要找到满足这个方程的一组整数解 \((x,k)\),并且 \(0 \le x \le 100, \ k \gt 0\),如果有多组解,需要输出 \(x\) 最大,\(k\) 最小的那一组。

由于 x 的取值范围很小(1 到 100),可以遍历所有可能的 x 值,然后对于每一个 x,尝试去求解 k。题目要求找 x 最大的解,因此,最聪明的遍历方式是从大到小遍历 x,即从 x = 100 开始,递减到 1。这样,找到的第一个满足条件的解,就一定是 x 最大的解。

将方程变形,用 x 和 n 来表示 k:\(1092k = n - 364x\)。使用一个 for 循环,让 x 从 100 递减到 1。在循环中,对于当前的 x,计算出 \(n-364x\) 的值。检查这个值是否能满足 \(k\) 的条件:\(n-364x\) 必须大于 \(0\)(因为 \(k \gt 0\));\(n-364x\) 必须能被 \(1092\) 整除(因为 \(k\) 必须是整数)。如果同时满足这两个条件,就找到了 \(x\) 最大的解。此时,计算出 \(k=(n-364x)/1092\),输出 \(x\)\(k\),然后用 break 结束循环,因为已经找到了题目要求的解。

参考代码
#include <cstdio>
int main()
{
    int n; scanf("%d", &n); // n 是需要筹集的目标总金额
    // 题目要求找到 x 最大的一组解,所以从 x 的最大可能值 100 开始,向下遍历
    for (int x = 100; x >= 0; x--) { // x 的范围是 1 <= x <= 100
        // 根据公式 n = 364*x + 1092*k,可以推导出 1092*k = n - 364*x
        // 先计算出 n - 364*x 的值
        int k = n - 364 * x;
        // 检查 k 是否有合法的正整数解
        // 1. 确保大于 0
        // 2. 确保可以被 1092 整除
        if (k > 0 && k % 1092 == 0) {
            // 如果条件满足,就找到了 x 最大的解
            k /= 1092; // 计算出 k 的值
            printf("%d\n%d\n", x, k); 
            break; // 因为是从大到小遍历 x 的,所以找到的第一个解就是最优解,可以直接跳出循环
        }
    }
    return 0;
}

P1420 最长连号

curl 记录当前连续递增序列的长度,用 maxl 记录已经发现的最长连续递增序列的长度。

从序列的第一个数字开始遍历,对于每个数字开始遍历。对于每个数字,将其与前一个数字进行比较。如果当前数字比前一个数字大 1(例如,前一个是 4,当前是 5),说明连续序列仍在继续,就将 curl 加 1。如果当前数字不比前一个数字大 1,说明连续序列中断了。此时,一个新的连续序列从当前数字开始,其长度为 1,所以将 curl 重置为 1。每次更新 curl 后,都将它与 maxl 进行比较。如果 curl 更大,就更新 maxl 的值。遍历完整个序列后,maxl 中存储的就是最终答案。

参考代码
#include <cstdio>
int main()
{
	int n; scanf("%d", &n);
	int last = -1; // 用于存储上一个读入的数字,初始化为题目数据范围之外的值
	int curl = 0; // 记录当前连号的长度
    int maxl = 1; // 记录最长连号的长度,任何序列的最长连号至少为 1(单个数字)
	for (int i = 1; i <= n; i++) { // 循环 n 次,依次处理序列中的每个数
		int x; scanf("%d", &x);
		// x就是当前的数,last就是上一个数
		if (i == 1 || x == last + 1) { // 判断是否构成连号
			curl++; // 连号长度增加
		} else { // 如果不连续,则连号中断,从当前数字开始一个新的连号
			curl = 1; 
		}
		if (curl > maxl) maxl = curl; // 更新最长连号
		last = x; // 处理完当前数字后,将它赋值给 last,供下一次循环使用
	}
	printf("%d\n", maxl);
	return 0;
}

P1319 压缩技术

由于只需要按顺序输出,并不需要提前存储整个矩阵,所以可以边读边输出,这样可以节省大量内存。

怎么知道什么时候输入结束了呢?题目保证了所有压缩码数字代表的数量之和恰好等于 \(n \times n\)

参考代码
#include <cstdio>
int main()
{
    int n;
    scanf("%d", &n);
    int num = 0; // 当前要打印的数字,0 或 1,根据题意,总是从打印 0 开始
    int x; // 用于临时存储从输入中读取的、表示连续数量的整数
    int cnt = 0; // 列计数器,用于判断何时需要换行
    int sum = 0; // 总计数器,用于判断何时结束程序
    while (sum != n * n) { // 当已打印的总数不等于 n*n 时,循环继续
        scanf("%d", &x); // 读取下一个表示连续数量的数
        for (int j = 0; j < x; j++) { // 内层循环:打印 x 次当前数字 num
            printf("%d", num); // 打印 0 或 1
            cnt++; // 列计数器加一
            if (cnt == n) { // 检查是否需要换行
                printf("\n"); // 如果当前行已满,则输出换行符
                cnt = 0; // 并将列计数器重置为 0
            }
        }
        num = 1 - num; // 切换要打印的数字(0 变 1,1 变 0)
        sum += x; // 更新已打印的总数
    }
    return 0;
}

习题:P8813 [CSP-J 2022] 乘方

解题思路

本题的核心是计算乘方并处理可能出现的数值溢出问题,由于 \(a\)\(b\) 的取值范围都很大(可达 \(10^9\)),首先需要考虑算法的效率和数据溢出两个关键点。

  1. 效率问题:一个朴素的想法是使用循环,将 \(a\) 连乘 \(b\) 次。如果 \(b\) 非常大,例如 \(10^9\),这样的循环会运行 \(10^9\) 次,导致超时。
  2. 溢出问题:题目明确要求,当结果超过 \(10^9\) 时,需要输出 \(-1\)。在计算过程中,乘积可能会溢出。

如何解决效率问题?

注意到,虽然 \(b\) 的值可能很大,但 \(a\)\(b\) 次方增长得非常快。只要 \(a \gt 1\),其幂次的结果会迅速超过 \(10^9\) 这个阈值。

  • \(a=2\) 时,\(2^{29} \approx 5 \times 10^8\),而 \(2^{30} \approx 10^9\)。这意味着循环最多执行约 30 次时,结果就会超过 \(10^9\)
  • \(a\) 更大时,例如 \(a=10\)\(10^9\) 就是阈值,循环最多执行 9 次。随着 \(a\) 的增大,循环次数会变得更少。

结论是:只要 \(a \gt 1\),最多只需要进行约 30 次乘法,就能确定结果是否会超过 \(10^9\)。因此,一个简单的循环实际上是足够快的,并不会因为 \(b\) 的值过大而超时。

如何解决溢出问题?

假设当前的累乘结果是 \(p\),下一步要计算 \(p \times a\)。为了防止 \(p \times a\) 这一步计算本身溢出,可以利用不等式的性质,去判断 \(p\) 是否大于 \(10^9 / a\)。这样,就可以在执行乘法之前,安全地预判结果是否会超出范围。

参考代码
#include <cstdio>

// 定义一个常量 INF 表示 10^9,作为溢出的判断阈值
const int INF = 1e9;

int main()
{
    int a, b;
    scanf("%d%d", &a, &b); // 读取输入的 a 和 b

    // 特殊情况处理:如果 a=1,a^b 的结果永远是 1
    if (a == 1) {
        printf("1\n");
    } else {
        int p = 1;
        // 循环 b 次,进行乘方计算
        for (int i = 1; i <= b; i++) {
            // 溢出判断:这是本题的核心。
            // 在执行 p * a 之前,先判断 p 是否已经大于 INF / a。
            // 如果 p > INF / a,那么 p * a 必然会 > INF,导致溢出。
            // 所以,只有在 ans <= INF / a 的情况下,乘法才是安全的。
            if (p <= INF / a) {
                p *= a; // 安全,执行乘法
            } else {
                // 如果不安全,说明即将溢出。将 p 标记为 -1 并跳出循环。
                p = -1; 
                break;
            }
        }
        // 输出最终结果(可能是计算值,也可能是溢出标记 -1)
        printf("%d\n", p);
    }
    return 0;
}

习题:P5726 [深基4.例9] 打分

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
    int n; // 评委人数
    scanf("%d", &n);
    int sum = 0; // 用于存储所有分数的总和
    int mx = 0; // 用于存储最高分
    int mn = 10; // 用于存储最低分
    for (int i = 0; i < n; ++i) { // 循环 n 次,读取每个评委的打分
        int x;
        scanf("%d", &x);
        sum += x; // 将当前分数累加到总和中
        mx = max(mx, x); // 更新最高分
        mn = min(mn, x); // 更新最低分
    }
    // 计算最终得分:(总分 - 最高分 - 最低分) / (评委人数 - 2)
    // 乘以 1.0 是为了将整数运算转换为浮点数运算,以得到小数结果
    printf("%.2f\n", 1.0 * (sum - mx - mn) / (n - 2));
    return 0;
}

习题:P9748 [CSP-J 2023] 小苹果

解题思路

“每隔 \(2\) 个取 \(1\) 个”等价于每 \(3\) 个看成一组,每组里取走第一个。所以如果某一轮开始前有 \(n\) 个苹果,这一轮会取走 \(\left\lceil \dfrac{n}{3} \right\rceil\) 个。因此针对第一个问题,可以循环迭代 \(n \leftarrow n - \left\lceil \dfrac{n}{3} \right\rceil\),直到 \(n\) 变成 \(0\),则循环次数就是取完的天数。这个循环次数最多大约是 \(\log_{1.5} n\),大约 \(50\) 多次循环。

对于第二个问题,最后一个苹果必然属于最后一组,要让它被取走,说明它是最后一组仅有的一个苹果。因此,拿走标号为 \(n\) 的苹果的那一天,当时拿之前苹果数量对 \(3\) 取余之后为 \(1\)

参考代码
#include <cstdio>
int main()
{
    int n; scanf("%d", &n);
    int ans = 0, days = 0;
    while (n > 0) {
        ans++;
        if (days == 0 && n % 3 == 1) days = ans; // 注意只有在第一次这样时记录答案
        n = n - (n + 2) / 3;
    }
    printf("%d %d\n", ans, days);
    return 0;
}
posted @ 2023-07-24 13:05  RonChen  阅读(107)  评论(0)    收藏  举报