#P2314. 第3题-参加博览会
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 926
            Accepted: 127
            Difficulty: 8
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年8月28日-国内
                              
                      
          
 
- 
                        算法标签>贪心算法          
 
第3题-参加博览会
题面描述:
有n场博览会将举办,第i场博览会的举办时间为从第starti天到第endi天(包含这两天)。小塔每天最多可以参加k场博览会,且不需要全程参加某场博览会,只需在某一天参加即可。给定博览会的数量n和每天最多可参加的博览会数量k,以及每场博览会的举办时间[starti,endi],请问小塔最多可以参加多少场博览会。输入包括两整数n和k,接下来n行是博览会的时间范围[starti,endi]。输出为小塔最多能参加的博览会数量。
题意化简
给定k个时间区间[x,y],从左到右每天只能选择k个时间区间。问最多能选多少个区间?
思路:贪心 + 优先队列
LeetCode 1353. 最多可以参加的会议数目的变种。
大家如果会做上面这个题,就会做本题。思想很简单。
贪心性质:对于第 i 天,如果有若干的会议都可以在这一天开,那么我们肯定是让 endDay 小的会议先在这一天开才会使答案最优,因为 endDay 大的会议可选择的空间是比 endDay 小的多的,所以在满足条件的会议需要让 endDay 小的先开。
做法:考虑随时间推移,维护一个按结束时间排序的小根堆,然后在每个时间戳,从堆里连续取k个出来解决掉即可。
解题思路
要解决这个问题,可以从以下两点进行考虑:
- 
贪心策略的选择:
- 在某一天,如果有多个博览会可以选择参加,优先选择结束时间(即endi)较早的博览会。因为结束时间较晚的博览会有更多天可以选择,结束时间较早的博览会选择的空间较小,这样可以避免浪费可选的天数。因此,每次都应该优先参加结束时间较早的博览会。
 
 - 
利用最小堆:
- 我们需要随时从当前可以参加的博览会中选择结束时间最早的几个。最小堆是一种合适的数据结构,因为它可以帮助我们在O(logn)的时间复杂度内找到最早结束的博览会。
 - 我们从起始的时间戳开始,按时间推移,每天从当前可参加的博览会中取出k场,并将它们从堆中移除,直到所有博览会都被处理完为止。
 
 
具体步骤
- 
输入数据:我们读取n个博览会的起止时间[starti,endi]。
 - 
排序:首先按照博览会的开始时间starti进行排序。这样可以保证我们在时间流逝的过程中,按顺序处理所有博览会。
 - 
最小堆的使用:我们使用一个最小堆
queue来存储当前可以参加的博览会的结束时间,堆的性质保证了我们总能先处理掉结束时间最早的博览会。 - 
贪心策略执行:
- 对于每一个时间戳,我们首先移除掉那些已经结束的博览会(即结束时间小于当前时间的博览会)。
 - 然后将当前可以参加的博览会加入到堆中(这些博览会的开始时间小于等于当前时间)。
 - 接着从堆中取出最多k个结束时间最早的博览会,记录参加的数量。
 
 - 
时间推进:如果某个时间戳没有博览会可参加,则将时间推进到下一个可以参加博览会的开始时间。
 
代码
python
import heapq
n, k = map(int, input().split())
a = [tuple(map(int, input().split())) for _ in range(n)]
# 根据开始时间排序
a.sort(key=lambda x: x[0])
# 模拟当前的时间
start = 0
# 优先队列,存储结束时间
queue = []
# 当前博览会的索引, 从0开始,从小往大遍历
idx = 0
# 参加的博览会数量
ans = 0
while idx < n or queue:
    # 移除已结束的博览会
    while queue and queue[0] < start:
        heapq.heappop(queue)
    # 添加当前可以参加的博览会
    while idx < n and a[idx][0] <= start:
        heapq.heappush(queue, a[idx][1])
        idx += 1
    # 如果没有可参加的博览会,更新开始时间
    if not queue:
        if idx == n:
            break
        start = max(start, a[idx][0])
    while idx < n and a[idx][0] <= start:
        heapq.heappush(queue, a[idx][1])
        idx += 1
    # 参加博览会
    for _ in range(k):
        if queue:
            ans += 1
            heapq.heappop(queue)
    start += 1
print(ans)
Java
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        int[][] input = new int[n][2];
        for (int i = 0; i < n; i++) {
            input[i][0] = sc.nextInt();
            input[i][1] = sc.nextInt();
        }
        sc.close();
        // 按开始时间排序
        Arrays.sort(input, (a, b) -> Integer.compare(a[0], b[0]));
        // 小根堆(按结束时间排序)
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1]));
        int i = 0, d = input[0][0], res = 0;
        while (i < n || !pq.isEmpty()) {
            // 添加所有开始时间小于等于当前时间 d 的事件到优先队列
            while (i < n && input[i][0] <= d) {
                pq.add(input[i++]);
            }
            // 移除所有结束时间小于当前时间 d 的事件
            while (!pq.isEmpty() && pq.peek()[1] < d) {
                pq.poll();
            }
            // 处理当前时间 d 上的最多 k 个事件
            int j = 0;
            while (!pq.isEmpty() && j < k) {
                pq.poll();
                res++;
                j++;
            }
            d++;
        }
        System.out.println(res);
    }
}
Cpp
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int main() {
    int n, k; // 输入博览会的数量n和每次可以参加的博览会数量k
    cin >> n >> k;
    
    vector<vector<int>> a(n, vector<int>(2)); // 用于存储博览会的开始和结束时间
    for (int i = 0; i < n; i++) {
        cin >> a[i][0] >> a[i][1]; // 读取开始和结束时间
    }
    // 根据开始时间排序
    sort(a.begin(), a.end(), [](const vector<int>& x, const vector<int>& y) {
        return x[0] < y[0];
    });
    int start = 0; // 当前时间
    priority_queue<int, vector<int>, greater<int>> queue; // 最小堆用于存储结束时间
    int idx = 0; // 当前处理的博览会索引
    int ans = 0; // 参加的博览会数量
    while (idx < n || !queue.empty()) {
        // 移除已结束的博览会
        while (!queue.empty() && queue.top() < start) {
            queue.pop();
        }
        // 添加当前可以参加的博览会
        while (idx < n && a[idx][0] <= start) {
            queue.push(a[idx][1]); // 将结束时间加入优先队列
            idx++;
        }
        // 如果没有可参加的博览会,更新开始时间
        if (queue.empty()) {
            if (idx == n) {
                break;
            }
            start = max(start, a[idx][0]);
        }
        while (idx < n && a[idx][0] <= start){
            queue.push(a[idx][1]);
            idx++;
        }
        // 参加博览会
        for (int i = 0; i < k; i++) {
            if (!queue.empty()) {
                ans++; // 参加博览会
                queue.pop(); // 弹出结束时间
            }
        }
        start++; // 增加当前时间
    }
    cout << ans << endl; // 输出参加的博览会数量
    return 0; // 结束程序
}
OJ会员可以通过点击题目上方《已通过》查看其他通过代码来学习。
题目内容
有n场编号从0到n−1的博览会将要举办,编号为i的博览会举办时间为[starti,endi],即从第starti天到第endi天,包含第starti天和第endi天。
小明计划参加这些博览会,每天最多可以参加k场博览会。请问小明最多可以参加多少场博览会。需注意,小明不需要全程参加一场博览会,只需要在某一天参加即可。
输入描述
第一行输入包含两个整数n和k,n表示博览会的数量,k表示每天最多可以参加的博览会的数量,1≤n≤104,1≤k≤10。以下n行每行包含两个整数starti和endi,表示第i场博览会的举办时间,1≤starti≤endi≤109。
输出描述
小明最多能参加的博览会数量。
样例1
输入
3 1
1 2
2 3
1 1
输出
3
解释
小明每天可以参加1场博览会,那么他可以在第1天参加第三场博览会,第2天参加第一场博览会,第3天参加第二场博览会,因此最多可以参加3场博览会。
样例2
输入
5 2
1 1
2 2
1 2
2 2
1 1
输出
4
解释
小明每天可以参加2场博览会,那么他可以在第1天参加第一场博览会和第五场博览会,第2天参加第二场博览会和第三场博览会,因此最多可以参加4场博览会。