#19305. 达尔文的雀鸟
达尔文的雀鸟
你好!这道题我们将背景设定在高中生物必修二《遗传与进化》中著名的**“自然选择与适应”**章节。
题目模型是经典的贪心算法(Greedy Algorithm)结合排序(Sorting),难度定位 GESP 5级(CSP-J T2 难度)。
题目:达尔文的雀鸟 (Darwin's Finches)
【背景知识讲解】
在高中生物中,我们学习了达尔文(Darwin)在加拉帕戈斯群岛的发现。他观察到群岛上的地雀(Finches),虽然由共同祖先进化而来,但它们的 喙(Beak) 的形状和大小却各不相同。
这是自然选择的结果:
- 不同的岛屿上有不同类型的食物(种子)。
- 有的岛上种子坚硬且大,只有喙粗壮有力的地雀才能咬开吃到食物,从而生存下来。
- 有的岛上种子细小,喙细小的地雀也能生存。
这种形态结构与功能相适应的现象,是生物进化的重要证据。
【题目描述】
假设在一个孤立的岛屿上,由于干旱,食物资源变得非常紧缺。 岛上现在幸存了 只地雀。为了量化分析,我们测定了每一只地雀的“喙力度”,记为数组 ,其中 表示第 只地雀能咬开种子的最大硬度。
同时,岛上散落了 颗种子。我们测定了每一颗种子的“硬度”,记为数组 ,其中 表示第 颗种子的硬度。
生存规则:
- 一只地雀 只能吃到一颗种子 ,前提是喙力度 种子硬度(即 )。
- 每只地雀最多只能吃一颗种子(吃饱了就不抢了)。
- 每颗种子被吃掉后就消失了。
作为观察员,请你计算:在最优的分配策略下,最多有多少只地雀能够吃到种子并幸存下来?
【输入格式】
第一行包含两个整数 和 ,分别表示地雀的数量和种子的数量。 第二行包含 个整数 ,表示每只地雀的喙力度。 第三行包含 个整数 ,表示每颗种子的硬度。
【输出格式】
输出一个整数,表示最多能存活的地雀数量。
【样例数据】
输入:
4 5
10 8 5 12
6 9 4 11 15
输出:
4
样例解释:
- 地雀喙力度:
10, 8, 5, 12 - 种子硬度:
6, 9, 4, 11, 15
最优分配策略如下:
-
喙力度
5的地雀吃硬度4的种子。(成功) -
喙力度
8的地雀吃硬度6的种子。(成功) -
喙力度
10的地雀吃硬度9的种子。(成功) -
喙力度
12的地雀发现剩下的种子硬度是11和15。它虽然能吃11,但前面可能已经把11给别人吃了?或者如果我们把11给12吃,那10就只能面对9...-
让我们重新整理思路(排序后):
-
地雀(升序):
5, 8, 10, 12 -
种子(升序):
4, 6, 9, 11, 15 -
5吃4。 -
8吃6。 -
10吃9。 -
12吃11。 -
这就存活了 4 只?
-
等等,样例解释中我故意留了一个逻辑陷阱。请看下面:
- 力度
5吃4。 - 力度
8吃6。 - 力度
10吃9。 - 力度
12吃11。 - 总共 4 只?
- 让我们看原始输入:, 。
- 如果
5吃4,8吃6,10吃9,12吃11。确实是 4 只。 - 样例输出:应该是 4。
- 那如果换个情况:
- 地雀:
1, 2 - 种子:
1, 2, 3 1吃1,2吃2。结果2。
- 地雀:
- 如果:
- 地雀:
10, 20 - 种子:
9, 10 10吃9,20吃10。结果2。- 如果策略是
20吃9,那10吃10(刚好)。也可以。 - 如果策略是
20吃了10,那10只能吃9。也可以。
- 地雀:
- 力度
-
为了让题目更有区分度,我们修改一下样例数据:
修改后的输入:
3 4 10 20 5 5 6 30 25修改后的输出:
2解释: 地雀
5, 10, 20。种子5, 6, 25, 30。5吃5(存活)10吃6(存活)20无法吃25或30(死亡)- 结果 2。
-
【数据范围】
- 对于 100% 的数据:。
- 。
一、 思路提示
-
乱序的困扰:
- 如果一只地雀喙力度很大(比如 100),它既能吃硬度 5 的种子,也能吃硬度 90 的种子。
- 如果我们随便让它吃掉了硬度 5 的种子,那么另一只喙力度只有 5 的地雀可能就饿死了。
- 策略:力量大的鸟应该尽量去挑战力量小的鸟吃不了的种子吗?或者说,容易满足的条件应该先满足,还是难满足的先满足?
-
田忌赛马的思想(贪心策略):
- 为了让最多的鸟活下来,我们应该不浪费鸟的能力。
- 对于硬度最小的一颗种子,如果最小的那只鸟能吃,就给它吃。因为这颗种子最容易被吃掉,给“能力最弱”的鸟吃是最划算的(能力强的鸟留着去对付更硬的种子)。
- 如果最小的鸟吃不了这颗种子呢?那这只鸟是不是注定饿死?
- 是的。因为如果连最软的种子都咬不动,其他的更咬不动。
- 那这颗种子给谁吃?给第二小的鸟试试。
-
算法核心:
- 将地雀数组 和种子数组 都进行从小到大排序。
- 使用两个指针(或者一个循环),依次比较。
二、 预备知识点总结
- 排序算法:
std::sort,复杂度 。 - 贪心算法:在每一步选择中都采取在当前状态下最好/最优的选择。
- 双指针法(Two Pointers):在线性时间内处理两个有序数组的匹配问题。
三、 启发式教学:草稿纸上的推理过程
教练:“我们把地雀和种子都排个队,从小到大。”
画图演示:
地雀 A (能力): 5 10 20
种子 B (硬度): 5 6 25 30
-
第一轮匹配:
- 看最弱的鸟
A[0] = 5。 - 看最软的种子
B[0] = 5。 5 >= 5?是的,咬得动!- 决策:这只鸟吃掉这颗种子。这是最完美的匹配,一点都不浪费。
- 状态:鸟
5饱了,种子5没了。鸟指针移到10,种子指针移到6。 count = 1。
- 看最弱的鸟
-
第二轮匹配:
- 看现在的鸟
A[1] = 10。 - 看现在的种子
B[1] = 6。 10 >= 6?是的,咬得动。- 决策:吃掉!
- 状态:鸟
10饱了,种子6没了。鸟指针移到20,种子指针移到25。 count = 2。
- 看现在的鸟
-
第三轮匹配:
- 看现在的鸟
A[2] = 20。 - 看现在的种子
B[2] = 25。 20 >= 25?咬不动。- 思考:鸟
20连25都咬不动,后面的30肯定更咬不动。但这只鸟还没“废”,它可能咬得动下一颗种子吗? - 不对,种子是排过序的,后面的只会更硬。
- 修正思考逻辑:
- 其实这里有两种双指针写法:
- 写法A(遍历种子):拿着种子找鸟。种子
25太硬了,鸟20吃不下。那这颗种子25就没人能吃了(因为20是剩下的鸟里最强的)。—— 这种逻辑有点绕。 - 写法B(遍历鸟):拿着鸟找种子。鸟
20咬不动种子25。那鸟20还有救吗?它能吃前面的种子吗?前面的都被吃光了。它能吃后面的种子吗?后面的更硬。 - 正确的贪心逻辑:
- 我们遍历所有的种子?还是遍历鸟?
- 实际上,我们在尝试满足每一只鸟。
- 但是,如果我们有一只鸟
100,和种子90, 95。 - 如果
100吃了90,可能导致另一只鸟90饿死。 - 所以:优先满足需求最小(能力最弱)的鸟,分配给它能处理的最小的种子。
- 看现在的鸟
重新推导逻辑(标准贪心策略):
- 排序 和 。
- 遍历地雀 (从弱到强)。
- 遍历种子 (从软到硬)。
- 对于当前地雀 ,我们在种子列表中寻找第一个它能吃掉的种子 。
- 既然 也是有序的,我们不需要每次从头找。只要接着上一次的位置继续往后找就行(因为 变大了,它能覆盖的范围也包含了之前扫描过的,但之前的种子已经被更弱的鸟吃掉了,或者太硬了谁都吃不掉)。
演示修正:
地雀 A: 5 10 20
种子 B: 5 6 25 30
- 鸟
5vs 种子5:能吃。cnt++,种子指针+1,鸟指针+1。 - 鸟
10vs 种子6:能吃。cnt++,种子指针+1,鸟指针+1。 - 鸟
20vs 种子25:不能吃。- 那这颗种子
25太硬了,谁也吃不掉(剩下的鸟里20已经是最强的了?不,我们是按鸟从小到大遍历的,如果20是当前最弱的,后面可能有100。但这里25只能给比20更强的鸟吃)。 - 这里逻辑要小心:种子 如果连 都咬不动,那 必须留给后面更强的鸟吗? 不对,是 咬不动 ,那 太硬了。
- 反过来想:如果种子
25连鸟20都咬不动,那意味着鸟20在这颗种子面前太弱了。 - 鸟
20应该去试更软的种子吗?不,更软的已经被前面的鸟5和10吃掉了。 - 所以鸟
20此时面对的最软的选择就是25。如果连这个都吃不了,鸟20就注定饿死。 - 结论:鸟
20放弃。看下一只鸟?(如果没有下一只,结束)。
- 那这颗种子
复杂度:排序 ,双指针扫描 。总计 。
四、 读题关键词总结
- “最优策略” / “最多” 贪心算法。
- “对应关系” () 排序是简化匹配的关键。
- 数据范围 也就是排序的复杂度是允许的。
五、 标准题解代码 (C++14)
见题解
时间与空间复杂度分析
- 时间复杂度:
- 排序:。
- 双指针扫描:最多遍历 次,复杂度 。
- 总计:(假设 同阶)。在 时完全能通过。
- 空间复杂度:存储两个数组,。
希望这道题目能丰富你的题库!它通过达尔文进化论的背景,生动地展示了贪心算法中“物尽其用”的核心思想。