Skip to content

喵哈喽,各位主人!今天由我,你们最可爱的小猫娘,来带大家攻克一道来自 Codeforces 的有趣题目:A. Collecting Beats is Fun。光听名字就感觉很好玩对不对?让我们一起来看看吧,喵~

题目大意 (Problem Description)

这道题是关于一个叫作 Kyubeat 的音乐游戏,是不是听起来就很酷?

游戏在一个 4x4 的面板上进行。面板上的一些格子会在特定的时间点亮起,玩家需要在正确的时间按下它们。

输入会给我们这些信息:

  1. 一个整数 k,表示一只手一次最多能同时按下 k 个面板。
  2. 一个 4x4 的字符矩阵,代表游戏的一个场景。
    • 如果格子上是数字 19,就表示这个面板需要在对应的时间点按下。比如 3 就代表在时间点 3 按下。
    • 如果格子上是 .,那就说明这个面板不需要按,可以无视它啦。

我们的任务是帮助一个叫“小黄瓜君”的玩家判断,他用自己的两只手,能不能在每个时间点都完美地按下所有需要按的面板。如果可以,就输出 "YES";如果不行,就输出 "NO",喵~

简单来说,就是在任何一个时间点,需要按下的面板总数,不能超过他两只手能按下的总和。只要所有时间点都满足这个条件,他就能成功啦!

题解方法 (Solution Approach)

哼哼,这道题看起来又是矩阵又是时间的,但其实是个纸老虎哦,喵~

仔细想一想,面板的具体位置重要吗?比如,在时间点 '1' 需要按下的两个面板,它们是挨在一起,还是在对角线上,对我们有影响吗?其实一点都没有啦!因为小黄瓜君的手非常灵活,只要数量足够,他可以按到任意位置的面板。

所以,这道题的关键根本不在于面板的 位置,而在于在 同一个时间点多少个 面板需要按。

这样一来,我们的思路就非常清晰了,喵~

  1. 统计数量:我们完全可以无视那个 4x4 的布局。我们要做的第一件事,就是统计在每个时间点(1 到 9),分别有多少个面板需要按下。我们可以准备一个计数器数组,比如 counts[10]counts[1] 用来存时间点 '1' 的面板数,counts[2] 存时间点 '2' 的,以此类推。

  2. 计算能力上限:小黄瓜君有两只手,每只手一次能按 k 个。所以,在任何一个时间点,他能按下的面板总数上限就是 2 * k 个。

  3. 逐一检查:接下来,我们从时间点 '1' 到 '9' 逐一检查。对于时间点 i,我们看看需要按下的面板数 counts[i] 是否超过了他的能力上限 2 * k

    • 如果 counts[i] > 2 * k,那就说明在第 i 个时间点,他忙不过来了呀!那挑战就失败了,好可怜的喵... 此时我们就可以直接判断结果为 "NO"。
    • 如果我们检查完所有时间点,都没有发现超过能力上限的情况,那就说明他每个时间点都能完美应对!太棒啦!结果就是 "YES"。

这个方法是不是非常直接呀?把复杂的问题简化,抓住核心矛盾,问题就迎刃而解啦!

题解 (Solution Code)

光说不练假把式,我们来看看代码是怎么实现这个思路的,的说!

cpp
#include <iostream>
#include <vector>
#include <string>

int main() {
    // 为了让程序跑得快一点,像小猫一样敏捷,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(nullptr);

    // 首先,读入 k,就是一只手能按多少个面板
    int k;
    std::cin >> k;

    // 创建一个大小为10的向量(可以当成数组用),用来记录1-9每个数字出现的次数
    // counts[0] 我们不用,counts[1] 存数字'1'的个数,以此类推
    std::vector<int> counts(10, 0);

    // 接下来读入4行4列的面板信息
    for (int i = 0; i < 4; ++i) {
        std::string row;
        std::cin >> row;
        // 遍历这一行的每个字符
        for (char c : row) {
            // 如果字符不是 '.',说明是个需要按的节拍
            if (c != '.') {
                // c - '0' 可以把字符'1'变成数字1,'2'变成2...
                // 然后在对应的计数器上加1
                counts[c - '0']++;
            }
        }
    }

    // 小黄瓜君有两只手,所以总能力是 2 * k
    int max_capacity = 2 * k;

    // 检查每个时间点(1到9)
    for (int i = 1; i <= 9; ++i) {
        // 如果在时间 i 需要按的面板数 > 他的总能力
        if (counts[i] > max_capacity) {
            // 那就肯定不行啦,输出 "NO" 然后结束程序
            std::cout << "NO\n";
            return 0;
        }
    }

    // 如果循环跑完了都没发现问题,说明他可以完成挑战!
    std::cout << "YES\n";

    return 0;
}

代码的逻辑和我们刚才想的一模一样,对吧?先用 counts 数组统计每个数字的出现次数,然后计算出 max_capacity,最后循环一遍进行比较,得出最终结论。非常清晰明了,喵~

通过这道题,我们可以学到一些很有用的小知识哦!

  1. 频率统计 (Frequency Counting) 这是算法题里超级常见的一种技巧!当题目需要我们关心某个元素出现了多少次时,就可以用一个数组或者哈希表来记录。就像这道题,我们用一个 counts 数组来记录每个节拍数字出现的次数。因为数字范围很小(1-9),用数组是最简单高效的啦。如果数字范围很大,我们可能就要考虑用 std::map 或者 std::unordered_map 了。

  2. 问题简化 (Problem Simplification) 很多题目会给一些看起来很复杂的信息,比如这个 4x4 的格子布局。一个重要的能力就是识别出哪些信息是核心,哪些是“烟雾弹”。在这道题里,格子的位置就是烟雾弹,我们把它忽略掉,问题就从一个二维问题变成了一个简单的一维计数问题,难度瞬间降低了好多呢,喵!学会去伪存真,是成为编程高手的必经之路哦。

  3. C++基础与优化

    • 代码里用到的 std::cinstd::cout 是 C++ 中最基本的输入输出流。
    • std::vector<int> counts(10, 0); 是一个创建并初始化向量的好方法,它创建了一个包含10个int元素的向量,并且所有元素都被初始化为0。
    • std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr); 这两行是为了解除 C++ 流与 C 标准I/O流的同步,并解除 cincout 的绑定,从而加快读写速度。在处理大量输入输出的竞赛题目中,这是个非常实用的小技巧!

好啦,这道题的讲解就到这里啦!是不是感觉很简单,很有成就感呢?只要抓住问题的核心,再复杂的题目也能被我们轻松解决掉,喵~ 主人下次遇到问题,也随时可以来找我哦!

Released under the MIT License.