题目描述
笔记本电脑在空闲时会进入休眠模式以节省能量。从休眠模式唤醒到活动模式需要消耗固定的能量,这个能量等于系统在单位时间内消耗的能量。因此,要最小化总能量消耗,就需要最小化系统从休眠模式唤醒的次数,即最小化空闲时段的数量。
我们面临的任务调度问题如下:有 nnn 个任务,每个任务 TiT_iTi 有一个释放时间 rir_iri 和一个截止时间 did_idi。每个任务需要在时间区间 [ri,di][r_i, d_i][ri,di] 内的某个整数时刻执行,且执行时间为 111 个单位时间。每个时刻只能执行一个任务。
题目保证以下条件:
- rir_iri 和 did_idi 都是整数,任务必须在整数时刻调度
- 当且仅当 di≤djd_i \leq d_jdi≤dj 时,有 ri≤rjr_i \leq r_jri≤rj
- 至少存在一个调度方案可以使所有任务在其时间区间内完成
目标是最小化空闲时段的数量。
输入格式
第一行包含测试用例的数量 TTT。
每个测试用例的第一行包含整数 nnn,表示任务数量。接下来的 nnn 行每行包含两个整数 rir_iri 和 did_idi,分别表示任务的释放时间和截止时间。
输出格式
对于每个测试用例,输出一行,包含最小空闲时段数。
题目分析
问题本质
这个问题可以理解为:在满足所有任务时间约束的前提下,将任务安排成尽可能少的连续执行段。每个连续段内的任务执行时间相邻,而不同段之间会产生空闲时段。
空闲时段的数量等于连续段的数量减 111。因此,我们的目标就是最小化连续段的数量。
关键观察
- 任务排序策略:将任务按照释放时间 rir_iri 排序,释放时间相同时按照截止时间 did_idi 排序
- 贪心思想:尽可能让任务连续执行,减少段间的间隔
- 调度策略:对于每个任务,如果能在当前连续段内安排,就继续扩展该段;否则开始新的连续段
算法思路
- 排序:将所有任务按释放时间升序排列,释放时间相同时按截止时间升序排列
- 初始化:设置当前连续段的右边界为第一个任务的截止时间
- 遍历处理:
- 如果当前任务的释放时间大于当前右边界,说明需要开始新的连续段,空闲时段数加 111,并更新右边界为当前任务的截止时间
- 否则,当前任务可以在当前连续段内执行,更新右边界为当前右边界加 111(表示执行该任务后时间推进)
- 输出结果:遍历完成后得到的空闲时段数即为答案
算法正确性证明
该贪心算法的正确性基于以下事实:
- 按释放时间排序确保了我们可以按时间顺序处理任务
- 当任务可以加入当前连续段时,我们总是选择加入,这保证了连续段尽可能长
- 只有当确实无法继续扩展当前段时才开启新段,这保证了段数最少
代码实现
// Laptop
// UVa ID: 1617
// Verdict: Accepted
// Submission Date: 2025-11-23
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
struct Task {
int r, d;
bool operator<(const Task& other) const {
if (r == other.r) return d < other.d;
return r < other.r;
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<Task> tasks(n);
for (int i = 0; i < n; i++)
cin >> tasks[i].r >> tasks[i].d;
sort(tasks.begin(), tasks.end());
int idleCount = 0;
int currentRight = tasks[0].d;
for (int i = 1; i < n; i++) {
if (tasks[i].r > currentRight) {
// 需要开始新的连续段
idleCount++;
currentRight = tasks[i].d;
} else {
if (tasks[i].d == currentRight) continue;
else currentRight++;
}
}
cout << idleCount << '\n';
}
return 0;
}
复杂度分析
- 时间复杂度:O(nlogn)O(n \log n)O(nlogn),主要来自排序操作
- 空间复杂度:O(n)O(n)O(n),用于存储任务信息
示例分析
样例 1
输入:
2
5
4 8
1 3
8 10
0 3
6 8
9
0 4
0 4
3 6
3 6
5 7
6 8
8 11
9 11
10 11
输出:
1
0
解释:
第一个测试用例中,任务排序后为:[0,3][0,3][0,3], [1,3][1,3][1,3], [4,8][4,8][4,8], [6,8][6,8][6,8], [8,10][8,10][8,10]。
调度过程:
- 第一个连续段:执行 [0,3][0,3][0,3] 和 [1,3][1,3][1,3],右边界推进到 333
- 需要新段:[4,8][4,8][4,8] 的释放时间 444 大于当前右边界 333,当前右边界更新为 888
- 继续执行 [6,8][6,8][6,8],右边界不变,仍为 888,执行 [8,10][8,10][8,10],右边界更新为 999
- 总共 111 个空闲时段
第二个测试用例中,所有任务可以安排在一个连续段内执行,因此空闲时段数为 000。
总结
本题通过贪心算法解决了任务调度中的最小化空闲时段问题。关键在于将任务按释放时间排序,并维护当前连续段的右边界,从而最小化连续段的数量。算法的时间复杂度为 O(nlogn)O(n \log n)O(nlogn),能够高效处理大规模数据。

260

被折叠的 条评论
为什么被折叠?



