题解:洛谷 P3611 Cow Dance Show

【题目来源】

洛谷:P3611 [USACO17JAN] Cow Dance Show S - 洛谷

【题目描述】

经过几个月的排练,奶牛们基本准备好展出她们的年度舞蹈表演。今年她们要表演的是著名的奶牛芭蕾——“cowpelia”。

表演唯一有待决定的是舞台的尺寸。一个大小为 \(K\) 的舞台可以支持 \(K\) 头牛同时在舞台上跳舞。在牛群中的 \(N\) 头牛按照她们必须出现在舞蹈中的顺序方便地编号为 \(1,2,\dots,N\)。第 \(i\) 头牛计划跳 \(d_i\) 的特定持续时间。
一开始,第 \(1,2,\dots,K\) 头牛出现在舞台上并开始跳舞。当这些牛中的某一头牛首先完成了她的部分,她会马上离开舞台并且第 \(K+1\) 头牛会出现在舞台上并开始跳舞。所以,舞台上总有 \(K\) 头奶牛在跳舞(直到表演的尾声,奶牛不够的时候)。当最后一头奶牛完成了她的舞蹈部分,表演结束,共花了 \(T\) 个单位时间。

显然,\(K\) 的值越大,\(T\) 就越小。由于表演不能拖太长,你得知了指定 \(T\) 的最大可能值的上限 \(T_{max}\)。请根据这个约束,确定 \(K\) 的最小值。

【输入】

第一行包括 \(N\)\(T_{max}\) 两个整数。

接下来的 \(N\) 行,第 \(i\) 行给出了第 \(i\) 头牛跳舞的持续时间 \(d_i\)。第 \(i\) 行包括一个整数 \(d_i\)

保证 \(K=N\) 时表演会按时完成。

【输出】

输出在表演时间不大于 \(T_{max}\) 时的 \(K\) 的最小可能值。

【输入样例】

5 8
4
7
8
6
4

【输出样例】

4

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛的舞蹈持续时间 \(d_i\),舞台可同时容纳 \(K\) 头牛跳舞。当某头牛跳完后立即离开,下一头牛补上,始终保持舞台上有 \(K\) 头牛(或剩余牛不足时)。求使得总表演时间 \(T \le T_{max}\) 的最小 \(K\)。这是一个整数二分 + 优先队列模拟问题,关键在于用二分搜索 \(K\),并用最小堆模拟 \(K\) 个并行通道的任务调度。

  2. 算法选择

    • 整数二分:在 \([1, N]\) 范围内二分查找最小可行的 \(K\)
    • 优先队列模拟:用最小堆维护 \(K\) 个舞台位置的当前完成时间,每次将新任务分配给最早空闲的位置
  3. 关键步骤

    • 读取输入\(N, T_{max}\)\(N\) 个持续时间 \(d_i\)
    • 二分初始化\(l = 1\)\(r = N\)
    • 二分过程\(l < r\)):
      • \(mid = (l + r) / 2\)
      • check(mid) 可行:\(r = mid\)
      • 否则:\(l = mid + 1\)
    • check 函数(模拟 \(K\) 个并行通道):
      • 初始化最小堆,\(K\) 个位置的完成时间均为 \(0\)
      • 遍历每头牛 \(i\)
        • 取出堆顶(最早空闲时间 \(t\)
        • \(t \mathrel{+}= d_i\),放回堆中
      • 遍历结束后,堆中最大值即为总完成时间
      • 返回 总完成时间 \(\le T_{max}\)
    • 输出 \(l\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\log N \cdot N \log K)\),二分 \(O(\log N)\) 轮,每轮模拟 \(O(N \log K)\)
    • 空间复杂度:\(O(K)\),优先队列
  5. 二分 + 优先队列模拟的核心思想

    • 并行调度模型\(K\) 个舞台位置等价于 \(K\) 个并行处理通道,每头牛的任务分配给最早空闲的通道
    • 贪心分配:每次将新任务分配给最早完成的通道,保证总完成时间最小(最优调度)
    • 二分单调性\(K\) 越大总时间越短,满足二分条件。找到最小 \(K\) 使得总时间 \(\le T_{max}\)
    • 最小堆高效维护\(O(\log K)\) 获取和更新最早空闲通道,避免线性扫描
    • 适用于"并行机器调度 + 最小化机器数"类问题,核心是二分搜索配合贪心模拟验证

【算法标签】

普及+ #整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
int n, T;
int d[N], ans;  // d: 任务时间数组

// 检查是否能用k个并行通道在时间T内完成所有任务
bool check(int k)
{
    priority_queue<int, vector<int>, greater<int>> q;  // 最小堆,存储每个通道的完成时间
    int res = -1;  // 最晚完成时间
    for (int i = 1; i <= k; i++)  // 初始化k个通道,初始完成时间为0
    {
        q.push(0);
    }
    for (int i = 1; i <= n; i++)  // 分配任务
    {
        int t = q.top();  // 取最早空闲的通道
        q.pop();
        t += d[i];  // 加上当前任务时间
        q.push(t);  // 放回队列
    }
    while (!q.empty())  // 找到最晚完成时间
    {
        res = max(res, q.top());
        q.pop();
    }
    return res <= T;  // 是否能在T时间内完成
}
int main()
{
    cin >> n >> T;  // 任务数, 时间限制
    for (int i = 1; i <= n; i++)
    {
        cin >> d[i];  // 每个任务的完成时间
    }

    int l = 1, r = n;  // 二分查找最少通道数
    while (l < r)
    {
        int mid = (l + r) / 2;
        if (check(mid))  // 如果mid个通道可行
        {
            r = mid;  // 尝试更少的通道
        }
        else
        {
            l = mid + 1;  // 需要更多通道
        }
    }
    cout << l << endl;  // 输出最少通道数
    return 0;
}

【运行结果】

5 8
4
7
8
6
4
4
posted @ 2026-08-29 09:37  团爸讲算法  阅读(4)  评论(0)    收藏  举报