CSP-J系列【2023】P9748 [CSP-J 2023] 小苹果题解

题目描述

小 Y 的桌子上放着 n 个苹果从左到右排成一列,编号为从 1 到 n。

小苞是小 Y 的好朋友,每天她都会从中拿走一些苹果。

每天在拿的时候,小苞都是从左侧第 1 个苹果开始、每隔 2 个苹果拿走 1 个苹果。随后小苞会将剩下的苹果按原先的顺序重新排成一列。

小苞想知道,多少天能拿完所有的苹果,而编号为 n 的苹果是在第几天被拿走的?

输入格式

输入的第一行包含一个正整数 n,表示苹果的总数。

输出格式

输出一行包含两个正整数,两个整数之间由一个空格隔开,分别表示小苞拿走所有苹果所需的天数以及拿走编号为 n 的苹果是在第几天。

输入输出样例

输入 #1复制

8

输出 #1复制

5 5

说明/提示

【样例 1 解释】

小苞的桌上一共放了 8 个苹果。
小苞第一天拿走了编号为 1、4、7 的苹果。
小苞第二天拿走了编号为 2、6 的苹果。
小苞第三天拿走了编号为 3 的苹果。
小苞第四天拿走了编号为 5 的苹果。
小苞第五天拿走了编号为 8 的苹果。

【样例 2】

见选手目录下的 apple/apple2.in 与 apple/apple2.ans。

【数据范围】

对于所有测试数据有:1≤n≤109。

测试点n≤特殊性质
1∼210
3∼5103
6∼7106
8∼9106
10109

特殊性质:小苞第一天就取走编号为 n 的苹果。

题目解释

给定编号1-n的苹果排成一列,每天从最左侧开始,每隔2个苹果取走1个(即取走第1,4,7,...个)。需要回答:

第n个苹果在第几天被取走

总共取走了多少个苹果

示例分析

输入n=8时:

  • 第1天取走1,4,7号 → 剩余2,3,5,6,8

  • 第2天取走2,5,8号 → 8号被取走

  • 第3天取走3,6号 输出:8号第2天被取走,共取走7个

解法一:直接模拟法

核心思想

用动态数组实时维护苹果序列,通过循环模拟每天取苹果的过程

复杂度分析

  • 时间复杂度:O(nlogn)

  • 空间复杂度:O(n)

完整代码

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<bool> apples(n, true);
    int days = 0, special_day = -1, total = 0;
    
    while (true) {
        bool has_apple = false;
        vector<int> to_remove;
        for (int i = 0; i < n; i += 3) {
            if (apples[i]) {
                to_remove.push_back(i);
                if (i == n - 1 && special_day == -1) {
                    special_day = days + 1;
                }
            }
        }
        if (to_remove.empty()) break;
        
        days++;
        total += to_remove.size();
        for (int idx : to_remove) {
            apples[idx] = false;
        }
    }
    cout << days << " " << special_day;
    return 0;
}

解法二:数学规律法

核心发现

  1. 每天剩余的苹果数构成递推关系:remain = remain - ceil(remain/3)

  2. 第n个苹果的位置变化遵循:pos = pos - (pos+2)/3

复杂度分析

  • 时间复杂度:O(logn)

  • 空间复杂度:O(1)

优化实现

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int days = 0, special_day = 0, remaining = n;
    int pos = n - 1; // 转换为0-based索引
    
    while (remaining > 0) {
        days++;
        int remove_cnt = (remaining + 2) / 3;
        if (pos % 3 == 0 && special_day == 0) {
            special_day = days;
        }
        remaining -= remove_cnt;
        pos -= (pos + 2) / 3; // 更新位置
    }
    cout << days << " " << special_day;
    return 0;
}

算法对比

维度

模拟法

数学法

时间复杂度

O(nlogn)

O(logn)

空间复杂度

O(n)

O(1)

适用场景

n≤1e6

n≤1e18

优势

直观易理解

高效精确

数学证明

设f(k)为第k天剩余苹果数:

  • 递推式:f(k) = f(k-1) - ⌈f(k-1)/3⌉

  • 当f(k-1)≡1 mod 3时:f(k)=2f(k-1)/3

  • 收敛速度:约log_{1.5}n天

算法总结报告

一、问题本质分析

该问题考察循环取余和数学建模能力,核心在于:

  1. 周期性取数规律(每3取1)

  2. 数列递推关系(剩余苹果数呈2/3递减)

  3. 特殊位置判定(第n个苹果的位置变化)

二、解法对比

维度

模拟法

数学推导法

时间复杂度

O(nlogn)

O(logn)

空间复杂度

O(n)

O(1)

适用数据规模

n ≤ 10⁶

n ≤ 10¹⁸

代码复杂度

简单直观

需要数学推导

调试难度

易调试

需要验证数学正确性

三、算法选择建议

  1. 竞赛应试:优先掌握数学推导法

  2. 教学演示:使用模拟法展示过程

  3. 工程实现:数学法+边界条件处理

四、延伸思考

  1. 变式1:环形排列时取模运算处理

  2. 变式2:动态间隔k值的通解公式

  3. 变式3:多维度取苹果问题(如矩阵排列)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值