喵~ 主人,今天我们来看一道来自 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
就会变得尽可能小!
所以,一个绝妙的贪心策略就诞生啦:
- 排序:先把所有队员按照力量值从大到小排个队,就像小猫排队领小鱼干一样,最厉害的排在最前面!
- 贪心选择:我们总是从当前还未被选走的队员中,选择最强的那一个来当新队伍的队长(也就是
P_max
的提供者)。 - 组队与计算:
- 选出队长后,计算以他为核心的队伍最少需要多少人 (
needed = D / P_leader + 1
)。 - 检查一下我们剩下的人数够不够
needed
这么多。 - 如果够,太棒啦!胜利次数
+1
,然后从总人数里扣掉这needed
个人。我们不需要关心具体是哪几个人,只需要知道用掉了多少人就行了。 - 如果不够,呜... 那就说明我们再也组不出能赢的队伍了。因为连当前最强的人当队长都不够人手了,比他更弱的人当队长需要的人只会更多,肯定也组不成了。所以这时候就可以直接结束啦!
- 选出队长后,计算以他为核心的队伍最少需要多少人 (
这样一步步下来,我们就能得到最多的胜利次数了,喵~
题解代码(C++)
这是实现上面思路的代码哦,主人请看~
#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<>()
),就可以实现从大到小或者从小到大的排序,非常方便!
希望本猫娘的讲解对主人有帮助!如果还有问题,随时可以再来问我哦,喵~