Skip to content

喵~ 主人,今天我们来看一道来自 Codeforces 的有趣问题,关于如何聪明地组建篮球队来赢得比赛!就让本猫娘来为你细细讲解吧,喵~

B. Basketball Together (Codeforces 1725B)


题目大意喵

简单来说,就是有一位名叫 Pak Chanek 的教练,他手下有 N 个候选队员,每个队员都有一个力量值 P_i。敌人队伍的力量是固定的 D

教练可以从这 N 个队员中组建任意数量的队伍,但每个队员最多只能加入一支队伍。

一支队伍要想战胜敌人,它的总力量值必须严格大于 D

最神奇、也是最关键的规则是:当一支队伍上场比赛时,教练有 special skill!他可以让队里所有队员的力量值,都变得和这支队伍中力量值最高的那个队员一样!

我们的任务就是,帮助教练计算一下,他最多能组建多少支可以获胜的队伍呢?喵~


解题思路的喵呜~

这个问题呀,一看到“最多”、“最优”这样的词,本猫娘的胡须就警觉起来了,这通常是贪心算法的信号哦!

我们来分析一下那个神奇的规则:如果一支队伍有 k 个队员,其中最强的队员力量是 P_max,那么这支队伍的总力量就会变成 k * P_max

要赢得比赛,就需要 k * P_max > D

我们来动动小脑筋,把这个不等式变个形:k > D / P_max。因为队员数量 k 必须是整数,所以一支队伍要赢,最少需要 floor(D / P_max) + 1 个队员。在C++的整数除法里,这恰好就是 D / P_max + 1 个队员。

我们的目标是赢得尽可能多的比赛,也就是组建尽可能多的获胜队伍。这就意味着,我们应该想办法让每一支队伍使用的人数尽可能少,这样才能省下更多的人去组建新的队伍,对不对呀?喵~

那么,怎样才能让需要的人数 k (= D / P_max + 1) 变得最小呢? 从公式里可以很清楚地看出来,只要让分母 P_max 尽可能k 就会变得尽可能

所以,一个绝妙的贪心策略就诞生啦:

  1. 排序:先把所有队员按照力量值从大到小排个队,就像小猫排队领小鱼干一样,最厉害的排在最前面!
  2. 贪心选择:我们总是从当前还未被选走的队员中,选择最强的那一个来当新队伍的队长(也就是 P_max 的提供者)。
  3. 组队与计算
    • 选出队长后,计算以他为核心的队伍最少需要多少人 (needed = D / P_leader + 1)。
    • 检查一下我们剩下的人数够不够 needed 这么多。
    • 如果够,太棒啦!胜利次数 +1,然后从总人数里扣掉这 needed 个人。我们不需要关心具体是哪几个人,只需要知道用掉了多少人就行了。
    • 如果不够,呜... 那就说明我们再也组不出能赢的队伍了。因为连当前最强的人当队长都不够人手了,比他更弱的人当队长需要的人只会更多,肯定也组不成了。所以这时候就可以直接结束啦!

这样一步步下来,我们就能得到最多的胜利次数了,喵~


题解代码(C++)

这是实现上面思路的代码哦,主人请看~

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

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

    // 这里就是本猫娘说过的,先把所有人的力量从大到小排序,喵~
    // std::greater<long long>() 能让 sort 从大到小排哦
    std::sort(p.begin(), p.end(), std::greater<long long>());

    int wins = 0;
    int players_used = 0;

    // 我们从最强的队员开始,一个一个地尝试让他们当队长
    for (int i = 0; i < n; ++i) {
        // 如果所有人都被分好队了,就不能再组新队了,循环当然要停下来喵
        if (players_used >= n) {
            break;
        }

        long long leader_power = p[i];
        
        // 这就是计算胜利需要的最少人数啦,就是 D 除以队长力量再加一
        // (在整数除法里,D / leader_power 会自动向下取整)
        long long players_needed = d / leader_power + 1;

        // 检查一下,剩下的队员够不够组成这支新队伍呢?
        // 已经用掉的人 + 这次需要的人 <= 总人数
        if (players_used + players_needed <= n) {
            wins++; // 够的话,胜利次数加一!
            players_used += players_needed; // 并且把用掉的人数记录下来
        } else {
            // 不够的话,就说明我们再也组不出能赢的队伍了
            // 因为更弱的队长需要的人只会更多,肯定也不够
            // 所以可以直接结束啦,喵呜~
            break;
        }
    }

    std::cout << wins << std::endl;
}

int main() {
    // 这两行是让输入输出变快的魔法,对付大数据时很有用哦
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    solve();

    return 0;
}

相关知识点介绍

贪心算法 (Greedy Algorithm)

贪心算法呀,就像一只看到小鱼干就马上扑过去的小猫,它在每一步选择中,都采取在当前状态下最好或最优的选择,期望这样一步步下来,最终的结果也是全局最好或最优的,喵~

在这个问题里,我们的“贪心”体现在:每次都选当前最强的队员当队长。 为什么这是好的呢?因为最强的队长能让组队所需的人数最少,这是对“节省人力”这一目标来说“当前最好”的选择。而事实也证明,这种局部最优的选择,最终也导向了全局最优解(也就是最多的胜利次数)!

不是所有问题都能用贪心解决的,但如果能证明它的正确性,贪心算法通常都非常高效和简洁,是解决问题的好帮手!

排序 (Sorting)

为了能每次都方便地找到“最强”的队员,排序是必不可少的第一步哦。就像整理毛线球一样,理顺了才好用嘛,喵~

C++ 的 std::sort 是一个非常强大的工具,通过传入不同的比较函数(比如 std::greater<>()),就可以实现从大到小或者从小到大的排序,非常方便!

希望本猫娘的讲解对主人有帮助!如果还有问题,随时可以再来问我哦,喵~

Released under the MIT License.