Skip to content

喵~ 主人,欢迎来到我的题解小课堂!今天我们要看的是一道关于机器人破解电脑的有趣问题,Codeforces 583B - Robot's Task。别担心,这道题虽然听起来有点复杂,但只要跟着我的思路走,就会发现它其实很简单的说!

题目大意

我们可爱的机器人 Doc 正面临一个任务:破解排成一行的 n 台电脑。

  • 这些电脑从左到右编号为 1 到 n
  • 要破解第 i 台电脑,机器人手中必须已经拥有至少 a[i] 份从其他电脑那里收集来的信息。
  • 机器人一开始在 1 号电脑旁边,手上没有任何信息。
  • 它可以在这一排电脑之间向左或向右移动,但每次改变方向都是很耗费资源的。

我们的目标是,帮助 Doc 找到收集全部 n 份信息所需要的最少方向改变次数。题目保证了,总有一种方法可以完成任务,所以不用担心无解的情况,喵~

题解方法

这个问题呀,最直观的解法就是模拟机器人的行动,并且在每一步都做出最“贪心”的选择。

“贪心”是什么意思呢?就是只顾眼前利益最大化!在这里,就意味着在机器人每一次单向移动(比如从左到右)的过程中,只要遇到可以破解的电脑,就绝不放过,立刻破解它!

为什么这样做是最好的呢? 你想呀,主人~ 机器人反正都要从头走到尾(或者从尾走到头)来检查每一台电脑。在路上,如果遇到一台满足破解条件(当前信息数 >= a[i])的电脑,我们破解它,就能立刻增加一份信息。这份信息又能帮助我们去破解下一台要求更高的电脑。如果我们跳过它,不仅这次白跑了一趟,之后还得再掉头回来破解它,那不是平白无故增加了移动成本和转向次数嘛?

所以,我们的策略就非常清晰了:

  1. 从左向右扫描:机器人从 1 号电脑开始,一路向右走到 n 号。途中只要遇到 !hacked[i] 并且 当前信息数 >= a[i] 的电脑,就立即破解,增加信息数。
  2. 判断是否完成:一趟走完后,检查是不是所有电脑都破解了。
    • 如果全部破解,任务完成!我们就得到了最终答案。
    • 如果没有,说明我们必须改变方向才能继续破解剩下的电脑。
  3. 改变方向:方向改变次数 +1
  4. 从右向左扫描:机器人从 n 号电脑开始,一路向左走到 1 号。同样,遇到能破解的就破解。
  5. 重复:再次判断任务是否完成。如果还没,就再改变一次方向,回到第 1 步。

就这样来来回回地“扫荡”,直到所有电脑都被破解为止。这个过程所记录的方向改变次数,就是我们要求的最小值啦,喵!

题解

下面我们来看看代码是怎么实现这个过程的吧,喵~

cpp
#include <iostream>
#include <vector>

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        std::cin >> a[i];
    }

    // 用一个布尔数组记录每台电脑是否被破解
    std::vector<bool> hacked(n, false);
    int hacked_count = 0; // 记录已破解的电脑数量
    int changes = 0;      // 记录方向改变次数,也就是我们的答案

    // true 表示从左向右, false 表示从右向左
    // 机器人从1号电脑开始,所以第一次扫描是从左向右
    bool moving_right = true;

    // 只要还没破解所有电脑,就继续循环
    while (hacked_count < n) {
        int pass_hacks = 0; // 记录在这一趟扫描中破解的电脑数

        if (moving_right) {
            // 从左向右扫描 (索引 0 到 n-1)
            for (int i = 0; i < n; ++i) {
                // 如果电脑没被破解过,并且我们手头的信息足够
                if (!hacked[i] && hacked_count >= a[i]) {
                    hacked[i] = true;   // 标记为已破解
                    hacked_count++;     // 总破解数+1
                    pass_hacks++;       // 本趟破解数+1
                }
            }
        } else { 
            // 从右向左扫描 (索引 n-1 到 0)
            for (int i = n - 1; i >= 0; --i) {
                if (!hacked[i] && hacked_count >= a[i]) {
                    hacked[i] = true;
                    hacked_count++;
                    pass_hacks++;
                }
            }
        }

        // 如果一整趟下来,一台电脑都没能破解
        // 这说明剩下的电脑我们暂时都破解不了
        // 因为题目保证有解,所以这种情况只会发生在所有电脑都破解完毕后
        // 此时,外层 while 循环的条件 hacked_count < n 会不满足,从而自然退出
        if (pass_hacks == 0) {
            break;
        }

        // 如果我们破解了一些电脑,但任务还没完成
        // 这就意味着必须掉头了
        if (hacked_count < n) {
            changes++; // 改变方向次数+1
            moving_right = !moving_right; // 切换方向
        }
    }

    std::cout << changes << std::endl;
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

代码的逻辑和我们刚才讨论的策略是完全一样的喵。

  • hacked 数组用来做标记,避免重复破解。
  • hacked_count 是我们贪心的资本,它越多,能破解的电脑就越多。
  • moving_right 是一个开关,控制着 for 循环是正向还是反向。
  • while (hacked_count < n) 控制着整个过程,直到任务完成。
  • 每次扫描结束后,如果任务还没完成(hacked_count < n),并且这次扫描有成果(pass_hacks > 0),就说明需要掉头,changes 加一。

知识点介绍

这道题主要涉及了两个很基础但很重要的算法思想:

  1. 贪心算法 (Greedy Algorithm) 贪心算法就像一只看到小鱼干就马上冲过去吃的猫咪,它在每一步决策时,都采取当前状态下最好或最优的选择,期望通过一系列局部最优选择,最终能达到全局最优解。在这道题里,我们的局部最优选择就是“在一次单向扫描中,破解所有能破解的电脑”。幸运的是,这个贪心策略对于本题是正确的!

  2. 模拟 (Simulation) 模拟就是“照着剧本演”。我们不寻找什么奇特的数学捷径,而是老老实实地根据题目描述的规则和过程,用代码将其一步步地复现出来。这道题我们模拟了机器人来回移动、判断、破解的全过程。写模拟题的关键在于细心,理清过程中的每一个细节,并用变量准确地记录状态(比如方向、已破解数量等)。

希望这篇题解能帮到主人哦!如果还有不懂的地方,随时可以再来问我,喵~

Released under the MIT License.