#P2977. 模拟工作队列(100分)
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 226
            Accepted: 52
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为od
                                
            
                      
          
 
- 
                        算法标签>模拟          
 
模拟工作队列(100分)
题解思路与方法
1. 问题分析
本题要求模拟一个工作队列的运作过程,工作队列有最大长度限制,当队列满时新任务会导致最老的任务被丢弃。执行者从1开始编号,执行者会从队列中取出任务并执行。任务执行完成的时刻由提交时刻和任务执行时间决定。
输入包括任务提交时刻和任务执行时间的序列,以及工作队列的最大长度和执行者数量。我们的目标是输出最后一个任务完成的时刻以及丢弃的任务数量。
2. 模拟过程
- 
任务队列管理:
- 使用队列来存储任务,队列的大小不能超过最大长度。
 - 当队列满时,新任务提交会使最老的任务被丢弃,若执行者空闲且恰好有新的任务提交时,则最老任务被丢弃并立即执行新任务。
 
 - 
执行者管理:
- 执行者执行任务并释放出来,执行者的空闲时间决定其能接取的下一个任务。
 - 使用一个优先队列来模拟执行者的工作状态,确保优先编号的执行者优先获得任务。
 
 - 
任务调度:
- 当任务的提交时刻到达时,若队列未满,则直接加入队列。
 - 执行者在空闲时从队列中取最老的任务开始执行,直到任务完成。
 
 - 
输出:
- 输出最后一个任务完成的时刻。
 - 输出丢弃的任务数量。
 
 
3. 复杂度分析
- 时间复杂度:每个任务有最多一次入队和出队操作,因此总的时间复杂度为O(N),其中N为任务的数量。
 - 空间复杂度:需要存储任务队列和执行者的状态,空间复杂度为O(M + E),其中M为最大队列长度,E为执行者数量。
 
代码实现
Python代码
from collections import deque
# 事件的定义
class Event:
    def __init__(self, time, type, exId, taskId):
        self.time = time   # 事件时间
        self.type = type   # 0: 完成事件, 1: 提交事件
        self.exId = exId   # 执行者ID, -1代表任务提交事件
        self.taskId = taskId  # 任务ID,完成事件为-1
    def __lt__(self, other):
        if self.time != other.time:
            return self.time < other.time
        if self.type != other.type:
            return self.type < other.type
        return self.exId < other.exId  # 完成事件按执行者ID排序
def simulate_work_queue(task_info, queue_limit, num_executors):
    # 任务队列最大长度,执行者数量
    tasks = [(task_info[i], task_info[i+1]) for i in range(0, len(task_info), 2)]
    N = len(tasks)
    
    # 所有任务的提交事件
    events = []
    for i, (arrive_time, exec_time) in enumerate(tasks):
        events.append(Event(arrive_time, 1, -1, i))  # type=1 表示任务提交
    
    # 按时间排序事件
    events.sort()
    
    # 空闲执行者(按ID排序)
    idle_executors = set(range(num_executors))
    
    # 等待队列,存任务ID
    task_queue = deque()
    
    discarded_count = 0
    last_finish_time = 0
    
    # 处理事件
    while events:
        event = events.pop(0)
        
        # 当前事件的时间
        current_time = event.time
        
        # 如果是任务提交事件
        if event.type == 1:
            task_id = event.taskId
            # 如果队列满了,丢弃最老的任务
            if len(task_queue) >= queue_limit:
                task_queue.popleft()
                discarded_count += 1
            # 将任务加入队列
            task_queue.append(task_id)
            
            # 尝试安排执行者执行任务
            while task_queue and idle_executors:
                # 分配任务
                executor_id = min(idle_executors)
                idle_executors.remove(executor_id)
                task_id = task_queue.popleft()
                task_arrival, task_duration = tasks[task_id]
                finish_time = current_time + task_duration
                last_finish_time = max(last_finish_time, finish_time)
                events.append(Event(finish_time, 0, executor_id, -1))  # 完成事件
                
        # 如果是任务完成事件
        elif event.type == 0:
            executor_id = event.exId
            # 执行者变为空闲
            idle_executors.add(executor_id)
            
            # 如果有任务等待,继续分配任务
            while task_queue and idle_executors:
                # 分配任务
                executor_id = min(idle_executors)
                idle_executors.remove(executor_id)
                task_id = task_queue.popleft()
                task_arrival, task_duration = tasks[task_id]
                finish_time = current_time + task_duration
                last_finish_time = max(last_finish_time, finish_time)
                events.append(Event(finish_time, 0, executor_id, -1))  # 完成事件
        
        # 按时间重新排序事件
        events.sort()
    return last_finish_time, discarded_count
# 读取输入
task_info = list(map(int, input().split()))
queue_limit, num_executors = map(int, input().split())
# 模拟并输出结果
last_finish, discarded = simulate_work_queue(task_info, queue_limit, num_executors)
print(last_finish, discarded)
Java代码
import java.util.*;
public class Main {
    static class Event implements Comparable<Event> {
        int time, type, executorId, taskId;
        Event(int time, int type, int executorId, int taskId) {
            this.time = time;
            this.type = type;
            this.executorId = executorId;
            this.taskId = taskId;
        }
        @Override
        public int compareTo(Event other) {
            if (this.time != other.time) return this.time - other.time;
            if (this.type != other.type) return this.type - other.type;
            return this.executorId - other.executorId;
        }
    }
    public static int[] simulateWorkQueue(int[] taskInfo, int queueLimit, int numExecutors) {
        int N = taskInfo.length / 2;
        int[][] tasks = new int[N][2];
        for (int i = 0; i < N; i++) {
            tasks[i][0] = taskInfo[2 * i];
            tasks[i][1] = taskInfo[2 * i + 1];
        }
        PriorityQueue<Event> eventQueue = new PriorityQueue<>();
        for (int i = 0; i < N; i++) {
            eventQueue.add(new Event(tasks[i][0], 1, -1, i));
        }
        TreeSet<Integer> idleExecutors = new TreeSet<>();
        for (int i = 0; i < numExecutors; i++) {
            idleExecutors.add(i);
        }
        Queue<Integer> taskQueue = new LinkedList<>();
        int discardedCount = 0, lastFinishTime = 0;
        while (!eventQueue.isEmpty()) {
            Event event = eventQueue.poll();
            int currentTime = event.time;
            if (event.type == 0) { // Task complete
                idleExecutors.add(event.executorId);
                while (!taskQueue.isEmpty() && !idleExecutors.isEmpty()) {
                    int taskId = taskQueue.poll();
                    int finishTime = currentTime + tasks[taskId][1];
                    lastFinishTime = Math.max(lastFinishTime, finishTime);
                    eventQueue.add(new Event(finishTime, 0, idleExecutors.pollFirst(), -1));
                }
            } else { // Task arrival
                if (taskQueue.size() >= queueLimit) {
                    taskQueue.poll();
                    discardedCount++;
                }
                taskQueue.offer(event.taskId);
                while (!taskQueue.isEmpty() && !idleExecutors.isEmpty()) {
                    int executorId = idleExecutors.pollFirst();
                    int taskId = taskQueue.poll();
                    int finishTime = currentTime + tasks[taskId][1];
                    lastFinishTime = Math.max(lastFinishTime, finishTime);
                    eventQueue.add(new Event(finishTime, 0, executorId, -1));
                }
            }
        }
        return new int[]{lastFinishTime, discardedCount};
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String[] input = scanner.nextLine().split(" ");
        int[] taskInfo = new int[input.length];
        for (int i = 0; i < input.length; i++) {
            taskInfo[i] = Integer.parseInt(input[i]);
        }
        String[] config = scanner.nextLine().split(" ");
        int queueLimit = Integer.parseInt(config[0]);
        int numExecutors = Integer.parseInt(config[1]);
        int[] result = simulateWorkQueue(taskInfo, queueLimit, numExecutors);
        System.out.println(result[0] + " " + result[1]);
        scanner.close();
    }
}
C++代码
#include <bits/stdc++.h>
using namespace std;
struct Event {
    int time;   // 发生时间
    int type;   // 0=>执行完成, 1=>任务到达
    int exId;   // 对于完成事件表示哪个执行者, 到达事件用 -1
    int taskId; // 对应任务编号, 完成事件用 -1
};
// 自定义排序规则
bool cmp(const Event &a, const Event &b){
    if(a.time != b.time) return a.time < b.time; 
    if(a.type != b.type) return a.type < b.type;
    // 如果都是完成事件, 按执行者ID升序
    return a.exId < b.exId;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // 读取第一行
    string line;
    getline(cin, line);
    vector<int> arr;
    {
        // 切分并转成int
        stringstream ss(line);
        int x;
        while(ss >> x) arr.push_back(x);
    }
    int N = arr.size() / 2;
    vector<pair<int,int>> tasks(N);
    for(int i=0; i<N; i++){
        tasks[i].first  = arr[2*i];     // arrive_time
        tasks[i].second = arr[2*i + 1]; // exec_time
    }
    // 读取第二行: M, E
    int M, E;
    cin >> M >> E;
    // 准备事件列表(先放所有到达事件)
    vector<Event> events;
    events.reserve(N*2 + 100); // 粗略开个空间
    for(int i=0; i<N; i++){
        events.push_back(Event{tasks[i].first, 1, -1, i});
    }
    // 排序(到达事件)
    sort(events.begin(), events.end(), cmp);
    // 我们需要在模拟过程中增加“执行完成事件”,
    // 因此用一个索引来遍历 + 动态插入: 可以先用list或别的结构
    // 为方便,这里演示用一个multiset来管理所有Event,边插入边弹出。
    multiset<Event, decltype(&cmp)> evq(cmp);
    for(auto &e : events) evq.insert(e);
    events.clear(); // 不再使用该vector
    
    // 等待队列(存任务ID)
    deque<int> q;
    
    // 空闲执行者(按ID升序) - 用一个有序容器,如set
    set<int> idleEx;
    for(int i=0; i<E; i++){
        idleEx.insert(i);
    }
    int discarded = 0;
    int lastFinish = 0;
    while(!evq.empty()){
        // 取最早的事件
        auto it = evq.begin();
        Event cur = *it;
        evq.erase(it);
        int timeNow = cur.time;
        int ttype   = cur.type;
        int exId    = cur.exId;
        int tId     = cur.taskId;
        if(ttype == 0){
            // 完成事件
            // exId执行者变空闲
            if(!q.empty()){
                int frontTask = q.front();
                q.pop_front();
                int finishT = timeNow + tasks[frontTask].second;
                lastFinish = max(lastFinish, finishT);
                // 插入新的完成事件
                evq.insert(Event{finishT, 0, exId, -1});
            } else {
                idleEx.insert(exId);
            }
        } else {
            // 到达事件
            if((int)q.size() == M){
                q.pop_front();
                discarded++;
            }
            q.push_back(tId);
            // 让当前空闲执行者尽可能取任务
            while(!idleEx.empty() && !q.empty()){
                int pick = *idleEx.begin();
                idleEx.erase(idleEx.begin());
                int frontTask = q.front();
                q.pop_front();
                int finishT = timeNow + tasks[frontTask].second;
                lastFinish = max(lastFinish, finishT);
                evq.insert(Event{finishT, 0, pick, -1});
            }
        }
    }
    cout << lastFinish << " " << discarded << "\n";
    return 0;
}
        题目内容
让我们来模拟一个工作队列的运作,有一个任务提交者和若干任务执行者,执行者从1开始编号。
- 提交者会在给定的时刻向工作队列提交任务,任务有执行所需的时间,执行者取出任务的时刻加上执行时间即为任务完成的时刻。
 - 执行者完成任务变为空闲的时刻会从工作队列中取最老的任务执行,若这一时刻有多个空闲的执行者,其中优先级最高的会执行这个任务。编号小的执行者优先级高。初始状态下所有执行者都空闲。
 - 工作队列有最大长度限制,当工作队列满而有新的任务需要加入队列时,队列中最老的任务会被丢弃。
 - 特别的,在工作队列满的情况下,当执行者变为空闲的时刻和新的任务提交的时刻相同时,队列中最老的任务被取出执行,新的任务加入队列。
 
输入描述
输入为两行。第一行为2N个正整数,代表提交者提交的N个任务的时刻和执行时间。第一个数字是第一个任务的提交时刻,第二个数字是第一个任务的执行时间,以此类推。用例保证提交时刻不会重复,任务按提交时刻升序排列。
第二行为两个数字,分别为工作队列的最大长度和执行者的数量。
两行的数字都由空格分隔。N不超过20,数字为不超过1000的正整数。
输出描述
输出两个数字,分别为最后一个任务执行完成的时刻和被丢弃的任务的数量,数字由空格分隔
样例1
输入
1 3 2 2 3 3
3 2
输出
7 0
样例2
输入
1 6 2 4 4 3 6 3
1 2
输出
10 0
样例3
输入
1 6 2 4 3 3 4 3 6 3
1 2
输出
10 1