{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 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와 크레이콘은 크기 n짜리 원형 큐((head + size) % n 자리에 넣기)를 써요 — 줄에는 늘 n개 이하예요.
크레이콘은 새목록(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·크레이콘의 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);