문제 : https://www.acmicpc.net/problem/17383
17383번: 옥토끼는 통신교육을 풀어라!!
옥토끼가 이런 식으로 문제를 풀면 tncks0121은 옥토끼가 5, 10, 15, 20, 25, 30, 34분에 문제를 풀었으므로 최대 5분동안 휴식을 한 것으로 간주한다.
www.acmicpc.net
문제 파악하기
옥토끼가 N개의 문제를 푸는데 필요한 시간 간격의 최댓값이 가장 작아지는 경우를 구하는 문제입니다. 옥토끼는 한 번에 2개의 문제를 풀 수 있으며, 선택한 문제는 끝까지 해결한다고 합니다. 입력된 예시(3 4 5 9 10 14 15)를 보면서 문제를 자세히 이해해보겠습니다. 만약 가장 단순하게 입력된 순서대로 문제를 선택해서 해결한다면 아래와 같은 결과가 나오게 됩니다.
A | 3(3초) | 5(8초) | 10(18초) | 15(33초) | |||||||||||||||||||||||||||||
B | 4(4초) | 9(13초) | 14(27초) |
이 경우 문제를 해결한 시간은 { 3초, 4초, 8초, 13초, 18초, 27초, 33초 } 가 되며, 간격의 최댓값은 9가 됩니다. 아쉽게도 주어진 입력에 대해 9라는 값는 가장 효율적인 값이 아닙니다. 그렇다면 문제를 해결하기 위해서는 어떤 방법을 선택할 수 있을까요?
문제 해결하기
이런 문제는 가장 간단한게 해결하는 방법부터 생각해볼 필요가 있습니다. 우리가 구해야 할 값은 시간 간격의 최댓값입니다. 이 값을 이제부터는 d라고 정의하겠습니다. 우리는 d의 값을 가장 단순하게 구하는 방법으로 1부터 하나씩 적용해보는 방법을 떠올릴 수 있습니다. 좋은 방법이지만 2가지 의문점이 떠오릅니다.
첫 번째, 문제를 d만큼의 간격을 두고 모두 해결할 수 있는지 어떻게 구할 수 있을까? 이는 가장 시간이 오래 걸리는 문제와 적게 걸리는 문제를 한 쌍으로 만들면 해결할 수 있습니다. 옥토끼는 한 번에 2개의 문제를 풀 수 있으니 일단 한 문제는 길게 걸리는 문제로 선정하고, 나머지는 짧게 걸리는 문제로 선정할 수 있습니다. 그리고 여기서 중요한 점은 최대한 짧게 걸리는 문제를 늦게 풀어야 합니다. 우리가 원하는 건 모든 문제를 가장 빠르게 해결하는 것이 아닙니다. 해결한 문제 사이의 간격이 가장 짧아야 합니다. 따라서, 우리는 짧게 걸리는 문제를 최대한 천천히 풀면서 길게 걸리는 문제를 해결할 시간을 벌 수 있습니다. 만약 최대한 시간을 벌어도 d만큼의 시간으로 부족하다면 우리는 d만큼의 시간 간격으로는 문제를 해결할 수 없다는 걸 유추할 수 있습니다. 이렇게 d에 대해 하나씩 시뮬레이션을 돌려보면 우리는 d의 최솟값을 구할 수 있습니다.
문제를 해결했다고 생각할 수 있지만 여기서 두 번째 의문점이 떠오를 수 있습니다. 1부터 숫자를 하나씩 확인하면 제한시간 안에 정답을 구할 수 있을까? 사실 1부터 하나씩 확인하면 당연히 제한 시간 안에 해결할 수 없습니다. 하지만 우리는 d의 값에 따른 결괏값이 어떻게 변화하는지 생각해보면 어렵지 않게 문제를 해결할 수 있습니다. d의 값에 따라 우리는 [ 가능/불가능 ] 이라는 결과를 얻을 수 있으며, 이 값은 d의 크기에 영향을 받습니다. d의 값은 정답이 되기 전까지는 항상 [ 불가능 ]입니다. 그리고 정답에 도달한 순간부터 항상 [ 가능 ]으로 바뀌게 됩니다. 그리고 이런 경우에는 이분 탐색을 활용할 수 있습니다. 이분 탐색을 통해 현재 값의 결과를 토대로 탐색의 범위를 줄이면 아무리 넓은 범위라도 효율적으로 정답을 구할 수 있습니다.
소스코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
#include <stdio.h>
#include <vector>
#include <utility>
#include <algorithm>
#define SWAP(a,b)(a=a^b, b=a^b, a=a^b)
using namespace std;
typedef long long INT;
int N, t, ll, rr, f;
vector< INT > inp;
INT l, r, mid;
INT a, b;
int main() {
scanf("%d", &N);
for(int i=1;i<=N;i++) {
scanf("%d", &t);
inp.push_back(t);
}
sort(inp.begin(), inp.end());
l = 1; r = 1e9;
while(l<=r) {
// mid는 문제를 푸는데 걸리는 틈
mid = (l+r)/2;
// 오래 걸리는 문제부터 풀기 >> a
// 오래 걸리는 문제가 풀리기 전까지 빨리 걸리는 문제 풀기 >> b
// 값은 항상 mid의 배수로 저장하기
a = ((inp[N-1] + (mid-1))/mid)*mid;
b = 0;
f = 1;
ll = 0;
rr = N-2;
while(ll<=rr and f) {
if(a-b == mid) {
SWAP(a, b);
a += ((inp[rr] + (mid-1))/mid)*mid;
rr--;
}
else {
if((inp[ll] + (mid-1))/mid > 1) f = 0;
else {
ll++;
b += mid;
}
}
}
if(!f or a-b>mid) l = mid+1;
else r = mid-1;
}
printf("%lld", l);
}
/*
3
1 1 10
ans: 4
*/
|
cs |
후기
그리디 알고리즘과 이분 탐색(매개 변수 탐색)이 결합된 문제입니다. 매개 변수 탐색 기법이 적용된 문제는 항상 흔적을 남기기 마련인데 이 문제 역시 얼추 예상할 수 있었던 문제입니다. 다만 결과를 도출하는 과정에서 그리디 알고리즘이 필요하기에 마냥 쉽다고 할 수 없는 문제였습니다. 그리디 알고리즘을 연습하는 사람에게 추천하고 싶은 문제입니다.
'문제 노트 > 백준' 카테고리의 다른 글
개미( BOJ 2136 ) (1) | 2023.12.29 |
---|---|
유클리드 게임( BOJ 4342 ) (1) | 2023.12.28 |
게리멘더링( BOJ 28433 ) (0) | 2023.11.06 |
하늘에서 떨어지는 1, 2, ... , R-L+1개의 별( BOJ 17353 ) (1) | 2023.10.10 |
0과 1( BOJ 8111 ) (0) | 2023.08.23 |