UVa 1617 Laptop

题目描述

笔记本电脑在空闲时会进入休眠模式以节省能量。从休眠模式唤醒到活动模式需要消耗固定的能量,这个能量等于系统在单位时间内消耗的能量。因此,要最小化总能量消耗,就需要最小化系统从休眠模式唤醒的次数,即最小化空闲时段的数量。

我们面临的任务调度问题如下:有 nnn 个任务,每个任务 TiT_iTi 有一个释放时间 rir_iri 和一个截止时间 did_idi。每个任务需要在时间区间 [ri,di][r_i, d_i][ri,di] 内的某个整数时刻执行,且执行时间为 111 个单位时间。每个时刻只能执行一个任务。

题目保证以下条件:

  1. rir_iridid_idi 都是整数,任务必须在整数时刻调度
  2. 当且仅当 di≤djd_i \leq d_jdidj 时,有 ri≤rjr_i \leq r_jrirj
  3. 至少存在一个调度方案可以使所有任务在其时间区间内完成

目标是最小化空闲时段的数量。

输入格式

第一行包含测试用例的数量 TTT

每个测试用例的第一行包含整数 nnn,表示任务数量。接下来的 nnn 行每行包含两个整数 rir_iridid_idi,分别表示任务的释放时间和截止时间。

输出格式

对于每个测试用例,输出一行,包含最小空闲时段数。

题目分析

问题本质

这个问题可以理解为:在满足所有任务时间约束的前提下,将任务安排成尽可能少的连续执行段。每个连续段内的任务执行时间相邻,而不同段之间会产生空闲时段。

空闲时段的数量等于连续段的数量减 111。因此,我们的目标就是最小化连续段的数量。

关键观察

  1. 任务排序策略:将任务按照释放时间 rir_iri 排序,释放时间相同时按照截止时间 did_idi 排序
  2. 贪心思想:尽可能让任务连续执行,减少段间的间隔
  3. 调度策略:对于每个任务,如果能在当前连续段内安排,就继续扩展该段;否则开始新的连续段

算法思路

  1. 排序:将所有任务按释放时间升序排列,释放时间相同时按截止时间升序排列
  2. 初始化:设置当前连续段的右边界为第一个任务的截止时间
  3. 遍历处理
    • 如果当前任务的释放时间大于当前右边界,说明需要开始新的连续段,空闲时段数加 111,并更新右边界为当前任务的截止时间
    • 否则,当前任务可以在当前连续段内执行,更新右边界为当前右边界加 111(表示执行该任务后时间推进)
  4. 输出结果:遍历完成后得到的空闲时段数即为答案

算法正确性证明

该贪心算法的正确性基于以下事实:

  • 按释放时间排序确保了我们可以按时间顺序处理任务
  • 当任务可以加入当前连续段时,我们总是选择加入,这保证了连续段尽可能长
  • 只有当确实无法继续扩展当前段时才开启新段,这保证了段数最少

代码实现

// 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(nlog⁡n)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(nlog⁡n)O(n \log n)O(nlogn),能够高效处理大规模数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值