题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 头奶牛的舞蹈持续时间 \(d_i\),舞台可同时容纳 \(K\) 头牛跳舞。当某头牛跳完后立即离开,下一头牛补上,始终保持舞台上有 \(K\) 头牛(或剩余牛不足时)。求使得总表演时间 \(T \le T_{max}\) 的最小 \(K\)。这是一个整数二分 + 优先队列模拟问题,关键在于用二分搜索 \(K\),并用最小堆模拟 \(K\) 个并行通道的任务调度。
-
算法选择:
- 整数二分:在 \([1, N]\) 范围内二分查找最小可行的 \(K\)
- 优先队列模拟:用最小堆维护 \(K\) 个舞台位置的当前完成时间,每次将新任务分配给最早空闲的位置
-
关键步骤:
- 读取输入:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(\log N \cdot N \log K)\),二分 \(O(\log N)\) 轮,每轮模拟 \(O(N \log K)\)
- 空间复杂度:\(O(K)\),优先队列
-
二分 + 优先队列模拟的核心思想:
- 并行调度模型:\(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
浙公网安备 33010602011771号