ALGORITHM NOTE2

BOJ 12764 - 싸지방에 간 준하

그냥 컴퓨터 많이 설치해 줘!

#algorithm#boj#gold#implementation#data-structures#greedy#set-map#simulation#priority-queue
아카이브로 돌아가기

문제 링크

문제

현재 대한민국 해군에 소속되어있는 준하는 문제를 풀기 위해 매일같이 사이버 지식 정보방 통칭 싸지방에 다닌다. 그러나 최근 문제가 생겼다. 싸지방에 사람이 몰려 컴퓨터 수가 모자라게 된 것이다. 이런 사태를 도저히 용납할 수 없었던 준하는 곧 전역하는 선임을 설득해 민원을 넣도록 하는 데 성공했다.

마침내 부대에서는 민원을 받아들이기로 하였고, 컴퓨터를 증설하기로 했다. 또한, 컴퓨터 간의 사용률에 따라 다른 성능의 컴퓨터를 설치하고자 한다.

하지만 예산이 부족해 사람 수 만큼 컴퓨터를 살 수가 없었다. 고심에 고심을 거듭한 준하는 모든 사람이 항상 정해진 시간에 싸지방을 이용한다는 사실을 발견했다.

컴퓨터가 있는 자리에는 1번부터 순서대로 번호가 매겨져 있다. 모든 사람은 싸지방에 들어왔을 때 비어있는 자리 중에서 번호가 가장 작은 자리에 앉는 것이 규칙이다.

준하가 발견한 사실과 이용 규칙을 가지고, 모든 사람이 기다리지 않고 싸지방을 이용할 수 있는 컴퓨터의 최소 개수와 자리별로 몇 명의 사람이 사용했는가를 구하시오.

입력

첫째 줄에 사람의 수를 나타내는 NN이 주어진다. (1N100,000)(1 \le N \le 100{,}000) 둘째 줄부터 NN개의 줄에 걸쳐서 각 사람의 컴퓨터 이용 시작 시각 PP와 종료 시각 QQ가 주어진다. (0P<Q1,000,000)(0 \le P < Q \le 1{,}000{,}000)

시작 시각이나 종료 시각이 다른 사람과 겹치는 경우는 없다.

출력

첫째 줄에 사람이 모든 사람이 기다리지 않아도 되는 컴퓨터의 최소 개수 XX를 출력한다.

둘째 줄에는 1번 자리부터 XX번 자리까지 순서대로 각 자리를 사용한 사람의 수를 띄어쓰기 간격으로 출력한다.

풀이

사용자들의 이용 시간이 주어졌을 때 필요한 컴퓨터 수와 각 컴퓨터의 사용 횟수를 구해야 한다. 시작 시간이 빠른 순서로 사람을 처리하면서, 이미 사용이 끝난 컴퓨터를 다시 쓸 수 있게 관리하면 된다.

코드에서는 먼저 이용 구간을 시작 시간 기준으로 정렬한다. 현재 사용 중인 컴퓨터는 종료 시간과 컴퓨터 번호를 담은 우선순위 큐 pq로 관리하고, 사용이 끝난 컴퓨터 번호는 작은 번호가 먼저 나오도록 별도의 최소 힙 computer에 넣는다.

새 사용자가 들어올 때 종료 시간이 현재 시작 시간 이하인 컴퓨터들을 모두 반환한 뒤, 빈 번호가 있으면 가장 작은 번호를 재사용하고 없으면 새 번호를 만든다. 이렇게 하면 항상 가능한 가장 작은 번호를 배정하면서도 전체 컴퓨터 수와 번호별 사용 횟수를 한 번에 구할 수 있다.

코드

cpp
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
 
int N;
vector<pair<int, int>> v;
int result[100001];
 
void solve() {
	priority_queue<int, vector<int>, greater<int>> computer;
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
 
	int idx = 0;
	for (int i = 0; i < N; i++) {
		while (!pq.empty() && v[i].first >= pq.top().first) {
			computer.push(pq.top().second);
			pq.pop();
		}
		
		if (computer.empty()) {
			pq.push({ v[i].second, idx });
			result[idx++]++;
		}
		else {
			pq.push({ v[i].second, computer.top() });
			result[computer.top()]++;
			computer.pop();
		}
	}
 
	cout << idx << '\n';
	for (int i = 0; i < idx; i++)
		cout << result[i] << " ";
}
 
int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	cin >> N;
	v.resize(N);
	for (int i = 0; i < N; i++)
		cin >> v[i].first >> v[i].second;
	sort(v.begin(), v.end());
	
	solve();
	return 0;
}

복잡도

  • 시간 복잡도: 정렬과 우선순위 큐 연산 때문에 O(NlogN)O(N \log N)이다.
  • 공간 복잡도: 사용 중인 컴퓨터와 빈 번호를 담는 우선순위 큐로 O(N)O(N)을 사용한다.

마무리

종료된 컴퓨터와 빈 번호를 따로 관리하면, 가장 작은 번호 재사용 규칙까지 자연스럽게 맞출 수 있다.