{ }codelper
队列模拟P0115

轮流工作的 CPU

一个 CPU 轮流执行 n 个进程。
时刻是从 0 开始的整数。
第 i 个进程在时刻 aᵢ 到达,总共执行 bᵢ 后结束。
到达时刻满足 a₁ ≤ a₂ ≤ … ≤ aₙ。
到达的进程排到等待队列的末尾,同一时刻到达的进程编号小的排在前面。
CPU 反复执行下面的规则,直到所有进程都结束。
1.
如果队列为空,就空闲到下一个进程到达的时刻,并让该时刻到达的进程排进队列。
2.
取出队首的进程,执行剩余工作量与时间片 Q 中较小的那个时长。
执行从当前时刻 t 开始,到经过这段时长后的时刻 t′ 为止。
3.
先让到时刻 t′ 为止(含 t′)已经到达但还没排队的进程排进队列。
4.
然后,如果刚执行的进程还有剩余工作,就让它排到队尾;如果已经完成,它的结束时刻就是 t′。
按进程编号顺序把每个进程的结束时刻用空格隔开输出在一行。
第一行给出 n 和 Q,接下来 n 行每行给出用空格隔开的 aᵢ 和 bᵢ。

输入
1 ≤ n ≤ 5,000,1 ≤ Q ≤ 10⁹,0 ≤ a₁ ≤ … ≤ aₙ ≤ 10⁹,1 ≤ bᵢ ≤ 10⁹,bᵢ ≤ 50 × Q(每个进程最多轮到 50 次)
输出
n 个结束时刻(一行,空格隔开)
样例 1 · stdin
3 2
0 5
1 3
2 1
stdout
9 8 5
样例 2 · stdin
2 3
0 4
10 2
stdout
4 12
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

这是照着规则一步步走的模拟,等待队列是从前面取、往后面放的队列。
最多轮 50 × 5,000 = 25 万次,所以不要让时钟一次加 1,而是按时间片跳,很快就结束。
队列的用法:Python collections.deque 的 popleft(),Java ArrayDeque 的 pollFirst()/addLast(),C# Queue<int> 的 Dequeue()/Enqueue(),C++ std::queue 的 front()/pop()/push()(pop() 不返回值,所以先读 front())。
JavaScript/TypeScript 没有现成的队列,容易用 shift(),但它会把后面的元素全部前移,所以只往数组里放,并移动队首下标 head。
C 和 Craykon 用大小为 n 的环形队列(在 (head + size) % n 处放入)——队列里始终不超过 n 个。
Craykon 用 新列表(n, 0) 建格子,并在函数里直接修改外面的变量 size、next。
四个陷阱:① 同一时刻的顺序——在时间片结束时刻 t′ 到达的进程排在刚执行的进程前面(先规则 3 后规则 4)。
顺序反了,例 1 就不是 9 8 5。
② 队列为空时必须把时钟移到下一次到达。
否则例 2 的第二个进程会在时刻 4 提前执行,得到 6。
③ 结束时刻可达 5×10¹²,Java、C#、C、C++ 需要 long/long long(JavaScript、TypeScript、Craykon 的 double 在 2^53 以内是精确的,Python 不会溢出)。
④ 从队尾取(栈)会让晚到的先执行。

Pythonmain.py · 38 行
import sysfrom collections import deque data = list(map(int, sys.stdin.read().split()))n, q = data[0], data[1]arrive = data[2::2]left = data[3::2]  # 남은 일 finish = [0] * nline = deque()t = 0nxt = 0  # 아직 줄에 서지 않은 첫 프로세스done = 0  def join_until(now):    """now까지 도착한 프로세스를 번호 차례로 줄에 세운다."""    global nxt    while nxt < n and arrive[nxt] <= now:        line.append(nxt)        nxt += 1  while done < n:    if not line:  # 줄이 비면 다음 프로세스가 올 때까지 쉰다        t = max(t, arrive[nxt])        join_until(t)    p = line.popleft()    run = min(q, left[p])    t += run    left[p] -= run    join_until(t)  # 이 시각까지 온 프로세스가 먼저 줄에 서고    if left[p] > 0:        line.append(p)  # 그 뒤에 방금 돈 프로세스가 선다    else:        finish[p] = t        done += 1print(" ".join(map(str, finish)))
JavaScriptmain.js · 40 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q] = data;const arrive = [];const left = []; // 남은 일for (let i = 0; i < n; i++) {  arrive.push(data[2 + 2 * i]);  left.push(data[3 + 2 * i]);} // 배열 + 앞 자리(head)로 큐를 만든다. shift()는 뒤의 것을 모두 당긴다.const line = [];let head = 0;const finish = new Array(n).fill(0);let t = 0;let next = 0; // 아직 줄에 서지 않은 첫 프로세스let done = 0; // now까지 도착한 프로세스를 번호 차례로 줄에 세운다function joinUntil(now) {  while (next < n && arrive[next] <= now) line.push(next++);} while (done < n) {  if (head === line.length) {    // 줄이 비면 다음 프로세스가 올 때까지 쉰다    t = Math.max(t, arrive[next]);    joinUntil(t);  }  const p = line[head++];  const run = Math.min(q, left[p]);  t += run;  left[p] -= run;  joinUntil(t); // 이 시각까지 온 프로세스가 먼저 줄에 서고  if (left[p] > 0) line.push(p); // 그 뒤에 방금 돈 프로세스가 선다  else {    finish[p] = t;    done++;  }}console.log(finish.join(" "));
TypeScriptmain.ts · 42 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q] = data;const arrive: number[] = [];const left: number[] = []; // 남은 일for (let i = 0; i < n; i++) {  arrive.push(data[2 + 2 * i]);  left.push(data[3 + 2 * i]);} // 배열 + 앞 자리(head)로 큐를 만든다. shift()는 뒤의 것을 모두 당긴다.const line: number[] = [];let head = 0;const finish: number[] = new Array<number>(n).fill(0);let t = 0;let next = 0; // 아직 줄에 서지 않은 첫 프로세스let done = 0; // now까지 도착한 프로세스를 번호 차례로 줄에 세운다function joinUntil(now: number): void {  while (next < n && arrive[next] <= now) line.push(next++);} while (done < n) {  if (head === line.length) {    // 줄이 비면 다음 프로세스가 올 때까지 쉰다    t = Math.max(t, arrive[next]);    joinUntil(t);  }  const p = line[head++];  const run = Math.min(q, left[p]);  t += run;  left[p] -= run;  joinUntil(t); // 이 시각까지 온 프로세스가 먼저 줄에 서고  if (left[p] > 0) line.push(p); // 그 뒤에 방금 돈 프로세스가 선다  else {    finish[p] = t;    done++;  }}console.log(finish.join(" "));
JavaMain.java · 53 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.StringTokenizer; public class Main {    static int n, next = 0; // next: 아직 줄에 서지 않은 첫 프로세스    static long[] arrive;    static ArrayDeque<Integer> line = new ArrayDeque<>();     // now까지 도착한 프로세스를 번호 차례로 줄에 세운다    static void joinUntil(long now) {        while (next < n && arrive[next] <= now) line.addLast(next++);    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        n = Integer.parseInt(st.nextToken());        long q = Long.parseLong(st.nextToken());        arrive = new long[n];        long[] left = new long[n]; // 남은 일        for (int i = 0; i < n; i++) {            st = new StringTokenizer(in.readLine());            arrive[i] = Long.parseLong(st.nextToken());            left[i] = Long.parseLong(st.nextToken());        }         long[] finish = new long[n];        long t = 0; // 5×10¹²까지 가서 long        int done = 0;        while (done < n) {            if (line.isEmpty()) { // 줄이 비면 다음 프로세스가 올 때까지 쉰다                t = Math.max(t, arrive[next]);                joinUntil(t);            }            int p = line.pollFirst();            long run = Math.min(q, left[p]);            t += run;            left[p] -= run;            joinUntil(t); // 이 시각까지 온 프로세스가 먼저 줄에 서고            if (left[p] > 0) line.addLast(p); // 그 뒤에 방금 돈 프로세스가 선다            else {                finish[p] = t;                done++;            }        }        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) out.append(i > 0 ? " " : "").append(finish[i]);        System.out.println(out);    }}
C#Main.cs · 52 行
using System;using System.Collections.Generic; class Program{    static void Main()    {        string[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(first[0]);        long q = long.Parse(first[1]);        long[] arrive = new long[n];        long[] left = new long[n]; // 남은 일        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            arrive[i] = long.Parse(p[0]);            left[i] = long.Parse(p[1]);        }         var line = new Queue<int>();        int next = 0; // 아직 줄에 서지 않은 첫 프로세스        // now까지 도착한 프로세스를 번호 차례로 줄에 세운다        void JoinUntil(long now)        {            while (next < n && arrive[next] <= now) line.Enqueue(next++);        }         long[] finish = new long[n];        long t = 0; // 5×10¹²까지 가서 long        int done = 0;        while (done < n)        {            if (line.Count == 0) // 줄이 비면 다음 프로세스가 올 때까지 쉰다            {                t = Math.Max(t, arrive[next]);                JoinUntil(t);            }            int p = line.Dequeue();            long run = Math.Min(q, left[p]);            t += run;            left[p] -= run;            JoinUntil(t); // 이 시각까지 온 프로세스가 먼저 줄에 서고            if (left[p] > 0) line.Enqueue(p); // 그 뒤에 방금 돈 프로세스가 선다            else            {                finish[p] = t;                done++;            }        }        Console.WriteLine(string.Join(" ", finish));    }}
C++main.cpp · 42 行
#include <algorithm>#include <iostream>#include <queue>#include <vector> int main() {    int n;    long long q;    std::cin >> n >> q;    std::vector<long long> arrive(n), left(n); // left: 남은 일    for (int i = 0; i < n; i++) std::cin >> arrive[i] >> left[i];     std::queue<int> line;    int next = 0; // 아직 줄에 서지 않은 첫 프로세스    // now까지 도착한 프로세스를 번호 차례로 줄에 세운다    auto joinUntil = [&](long long now) {        while (next < n && arrive[next] <= now) line.push(next++);    };     std::vector<long long> finish(n);    long long t = 0; // 5×10¹²까지 가서 long long    int done = 0;    while (done < n) {        if (line.empty()) { // 줄이 비면 다음 프로세스가 올 때까지 쉰다            t = std::max(t, arrive[next]);            joinUntil(t);        }        int p = line.front(); // pop()은 값을 돌려주지 않아서 front()를 먼저 읽는다        line.pop();        long long run = std::min(q, left[p]);        t += run;        left[p] -= run;        joinUntil(t); // 이 시각까지 온 프로세스가 먼저 줄에 서고        if (left[p] > 0) line.push(p); // 그 뒤에 방금 돈 프로세스가 선다        else {            finish[p] = t;            done++;        }    }    for (int i = 0; i < n; i++) std::cout << (i ? " " : "") << finish[i];    std::cout << "\n";}
Cmain.c · 55 行
#include <stdio.h> #define MAXN 5000 static long long arrive[MAXN], left[MAXN], finish[MAXN];/* 크기 MAXN인 원형 큐: 줄에는 많아야 n개만 있다 */static int line[MAXN];static int head = 0, size = 0;static int n, next = 0; /* next: 아직 줄에 서지 않은 첫 프로세스 */ static void push(int p) {    line[(head + size) % MAXN] = p;    size++;} static int pop(void) {    int p = line[head];    head = (head + 1) % MAXN;    size--;    return p;} /* now까지 도착한 프로세스를 번호 차례로 줄에 세운다 */static void join_until(long long now) {    while (next < n && arrive[next] <= now) push(next++);} int main(void) {    long long q;    scanf("%d %lld", &n, &q);    for (int i = 0; i < n; i++) scanf("%lld %lld", &arrive[i], &left[i]);     long long t = 0; /* 5×10¹²까지 가서 long long */    int done = 0;    while (done < n) {        if (size == 0) { /* 줄이 비면 다음 프로세스가 올 때까지 쉰다 */            if (arrive[next] > t) t = arrive[next];            join_until(t);        }        int p = pop();        long long run = left[p] < q ? left[p] : q;        t += run;        left[p] -= run;        join_until(t); /* 이 시각까지 온 프로세스가 먼저 줄에 서고 */        if (left[p] > 0) {            push(p); /* 그 뒤에 방금 돈 프로세스가 선다 */        } else {            finish[p] = t;            done++;        }    }    for (int i = 0; i < n; i++) printf(i ? " %lld" : "%lld", finish[i]);    putchar('\n');    return 0;}
Craykonmain.cray · 62 行
첫줄 = 控制台.读数字();n = 첫줄[0];q = 첫줄[1];arrive = 新列表(n, 0);left = 新列表(n, 0);循环 (i = 0; i < n; i++){    줄 = 控制台.读数字();    arrive[i] = 줄[0];    left[i] = 줄[1];} // 원형 큐: 줄에는 많아야 n개라 n칸이면 된다. head는 맨 앞 자리, size는 줄 길이.line = 新列表(n, 0);head = 0;size = 0;// 아직 줄에 서지 않은 첫 프로세스next = 0; // now까지 도착한 프로세스를 번호 차례로 줄에 세운다줄세우기(now){    当 (next < n && arrive[next] <= now)    {        line[(head + size) % n] = next;        size += 1;        next += 1;    }} finish = 新列表(n, 0);t = 0;done = 0;当 (done < n){    如果 (size == 0)    {        // 줄이 비면 다음 프로세스가 올 때까지 쉰다        t = 数学.较大值(t, arrive[next]);        줄세우기(t);    }    p = line[head];    head = (head + 1) % n;    size -= 1;    run = 数学.较小值(q, left[p]);    t += run;    left[p] -= run;    // 이 시각까지 온 프로세스가 먼저 줄에 서고    줄세우기(t);    如果 (left[p] > 0)    {        // 그 뒤에 방금 돈 프로세스가 선다        line[(head + size) % n] = p;        size += 1;    }    否则    {        finish[p] = t;        done += 1;    }}控制台.输出(finish);