题解:洛谷 P4379 Lemonade Line

【题目来源】

洛谷:P4379 [USACO18OPEN] Lemonade Line S - 洛谷

【题目描述】

这是农场上一个炎热的夏日,Farmer John 要给他的 \(N\) 头奶牛发柠檬汽水了!所有的 \(N\) 头奶牛(编号为 \(1 \dots N\))都喜欢柠檬汽水,只是有些喜欢的程度更高一些。具体来说,奶牛 \(i\) 为了获得柠檬汽水,最多愿意排在 \(w_i\) 头奶牛之后。现在所有的 \(N\) 头奶牛都在田里,但只要 Farmer John 敲响牛铃,这些奶牛就会立刻赶到柠檬汽水站。她们会在 Farmer John 开始分发柠檬汽水之前到达,但没有两头奶牛会在同一时刻到达。此外,当奶牛 \(i\) 到达时,当且仅当队伍中至多有 \(w_i\) 头奶牛时,她才会加入队伍。

Farmer John 想要提前准备一定量的柠檬汽水,但他不想浪费。排队的奶牛数量可能取决于她们的到达顺序。请帮助他求出在所有可能的到达顺序下,最小的可能排队奶牛数量。

【输入】

第一行包含 \(N\),第二行包含 \(N\) 个用空格分隔的整数 \(w_1, w_2, \dots, w_N\)。输入保证 \(1 \leq N \leq 10^5\),此外对于每头奶牛 \(i\)\(0 \leq w_i \leq 10^9\)

【输出】

输出在所有可能的奶牛到达顺序下,最小的可能排队奶牛数量。

【输入样例】

5
7 1 400 2 2

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛,每头奶牛 \(i\) 最多愿意排在 \(w_i\) 头奶牛之后。奶牛按到达顺序依次决定是否入队,当且仅当当前队伍中已有奶牛数量 \(\leq w_i\) 时才会加入。目标是安排到达顺序,使得最终排队奶牛数量最小

  2. 算法选择

    • 贪心策略:将奶牛按 \(w_i\) 从大到小排序,让"容忍度最高"的奶牛先到达
    • 关键观察:若当前队伍已有 \(k\) 头奶牛,新到达的奶牛 \(i\) 愿意入队的条件是 \(w_i \geq k\)。为了让尽可能少的奶牛入队,应该让 \(w_i\) 大的奶牛优先到达,因为她们的容忍度高,更容易在队伍较长时仍愿意加入
  3. 关键步骤

    • 排序:将所有奶牛的 \(w_i\)降序排列
    • 贪心遍历:按排序后的顺序模拟奶牛到达,维护当前队伍中已有的奶牛数量 \(cnt\)(初始为 \(0\)
    • 入队判定:对于第 \(i\) 个到达的奶牛(排序后),若 \(w_i \geq cnt\)(即她愿意排在至少 \(cnt\) 头奶牛之后),则她加入队伍,\(cnt++\)
    • 终止条件:若某头奶牛 \(w_i < cnt\),则她及之后所有奶牛都无法入队(因为后续奶牛的 \(w\) 更小或相等,队伍只会更长),直接终止遍历
    • 输出答案\(cnt\) 即为最小排队数量
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),遍历 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储 \(w\) 数组
  5. 贪心排序的核心思想

    • 优先让"宽容者"先到达\(w_i\) 大的奶牛对队伍长度容忍度高,先让她们到达可以"占据"队伍位置,阻止 \(w_i\) 小的奶牛后续入队
    • 反证法验证:假设存在最优解中某头 \(w\) 大的奶牛排在 \(w\) 小的奶牛之后,交换两者顺序后,\(w\) 大的奶牛仍然能入队(因为前面队伍更短),而 \(w\) 小的奶牛可能因前面变长而无法入队,结果不会变差
    • 单调性利用:排序后,若第 \(i\) 个奶牛无法入队,则后续所有奶牛的 \(w\) 更小,必然也无法入队,可直接终止
    • 问题转化:本质上是在寻找一个最长前缀,使得排序后第 \(i\) 个元素满足 \(w_i \geq i-1\)
    • 适用于"按某种属性排序后贪心选择"的排队、调度类问题

【解题思路】

【算法标签】

普及- #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, w[100005], ans=0;
bool cmp(int x, int y)
{
    return x>y;
}
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 输入n头奶牛的wi
        cin >> w[i];
    }
    sort(w+1, w+n+1, cmp);  // 按照从大到小方式排序
    for (int i=1; i<=n; i++) {  // 遍历n头奶牛
        if (w[i]>=i-1) ans++;  // 如果wi大于等于i-1,即大于等于队伍中的奶牛数,则说明这头奶牛可以进入队伍中
        else break;  // 否则结束循环(后面都肯定进不了队伍了)
    }
    cout << ans << endl;  // 输出结果
    return 0;
}

【运行结果】

5
7 1 400 2 2
3
posted @ 2026-08-29 09:37  团爸讲算法  阅读(9)  评论(0)    收藏  举报