이 포스팅은 Algorithm Problem Solving 시리즈 113 편 중 77 번째 글 입니다.

  • Part 1 - 백준(1003번): 피보나치 함수
  • Part 2 - 백준(1010번): 다리놓기
  • Part 3 - 백준(1012번): 유기농 배추
  • Part 4 - 백준(10159번): 저울
  • Part 5 - 백준(10164번): 격자상의 경로
  • Part 6 - 백준(1018번): 체스판 다시 칠하기
  • Part 7 - 백준(1018번): 체스판 다시 칠하기
  • Part 8 - 백준(1022번): 소용돌이 예쁘게 출력하기
  • Part 9 - 백준(10775번): 공항
  • Part 10 - 백준(10816번): 숫자 카드2
  • Part 11 - 백준(10816번): 숫자카드 2
  • Part 12 - 백준(10819번): 차이를 최대로
  • Part 13 - 백준(10830번): 행렬 곱셈
  • Part 14 - 백준(10844번): 쉬운 계단수
  • Part 15 - 백준(10868번): 최소값
  • Part 16 - 백준(1092번): 배
  • Part 17 - 백준(11003번): 최솟값 찾기
  • Part 18 - 백준(1102번): 발전소
  • Part 19 - 백준(11048번): 이동하기
  • Part 20 - 백준(11053번): 가장 긴 증가하는 부분 수열
  • Part 21 - 백준(11054번): 가장 긴 바이토닉 부분 수열
  • Part 22 - 백준(11055번): 가장 긴 증가하는 부분 수열2
  • Part 23 - 백준(11057번): 오르막 수
  • Part 24 - 백준(1120번): 문자열
  • Part 25 - 백준(11403번): 경로 찾기
  • Part 26 - 백준(11404번): 플루이드
  • Part 27 - 백준(1149번): RGB 거리
  • Part 28 - 백준(11559번): puyo puyo
  • Part 29 - 백준(11655번): ROT13
  • Part 30 - 백준(1167번): 트리의 지름
  • Part 31 - 백준(11722번): 가장 감소하는 부분 수열
  • Part 32 - 백준(12015번): 가장 긴 증가하는 부분 수열(LIS) 2
  • Part 33 - 백준(12851번): 숨바꼭질2
  • Part 34 - 백준(12852번): 1로 만들기 2
  • Part 35 - 백준(12865번): 평범한 배낭
  • Part 36 - 백준(1300번): K번째 수
  • Part 37 - 백준(1325번): 효율적인 해킹
  • Part 38 - 백준(13549번): 숨바꼭질3
  • Part 39 - 백준(13913번): 숨바꼭질4
  • Part 40 - 백준(14002번): 가장 긴 증가하는 부분 수열 4
  • Part 41 - 백준(1431번): 시리얼 넘버
  • Part 42 - 백준(1436번): 영화감독 숌
  • Part 43 - 백준(14499번): 주사위 굴리기
  • Part 44 - 백준(14888번): 연산자 끼워넣기
  • Part 45 - 백준(14889번): 스타트와 링크
  • Part 46 - 백준(14891번): 톱니바퀴
  • Part 47 - 백준(15658번): 연산자 끼워넣기 2
  • Part 48 - 백준(15686번): 치킨 배달
  • Part 49 - 백준(1697번): 숨바꼭질
  • Part 50 - 백준(1697번): 숨바꼭질
  • Part 51 - 백준(1707번): 이분 그래프(Bipartite Graph)
  • Part 52 - 백준(1708번): 볼록 껍질
  • Part 53 - 백준(17136번): 색종이 붙이기
  • Part 54 - 백준(1717번): 집합의 표현
  • Part 55 - 백준(17298번): 오큰수
  • Part 56 - 백준(17626번): Four Squares
  • Part 57 - 백준(18870번): 좌표 압축
  • Part 58 - 백준(1890번): 점프
  • Part 59 - 백준(1918번): 후위 표기식
  • Part 60 - 백준(1929번): 소수 구하기
  • Part 61 - 백준(1963번): 소수 경로
  • Part 62 - 백준(1965번): 상자넣기
  • Part 63 - 백준(1976번): 여행가자
  • Part 64 - 백준(1987번): 알파벳
  • Part 65 - 백준(1992번): 쿼드트리
  • Part 66 - 백준(2003번): 수들의 합2
  • Part 67 - 백준(20040번): 사이클 게임
  • Part 68 - 백준(2042번): 구간 합 구하기
  • Part 69 - 백준(2108번): 통계학
  • Part 70 - 백준(2110번): 공유기 설치
  • Part 71 - 백준(2156번): 포도주 시식
  • Part 72 - 백준(2193번): 이친수
  • Part 73 - 백준(2231번): 분해합
  • Part 74 - 백준(2250번): 트리의 높이와 너비
  • Part 75 - 백준(2293번): 동전 1
  • Part 76 - 백준(2294번): 동전 2
  • Part 77 - This Post
  • Part 78 - 백준(2468번): 안전 영역
  • Part 79 - 백준(2512번): 예산
  • Part 80 - 백준(2529번): 부등호
  • Part 81 - 백준(2565번): 전깃줄
  • Part 82 - 백준(2581번): 소수
  • Part 83 - 백준(2583번): 영역구하기
  • Part 84 - 백준(2630번): 색종이 만들기
  • Part 85 - 백준(2644번): 촌수계산
  • Part 86 - 백준(2667번): 단지번호붙이기
  • Part 87 - 백준(2751번): 수 정렬하기 2
  • Part 88 - 백준(2798번): 블랙잭
  • Part 89 - 백준(2904번): 수학은 너무 쉬워
  • Part 90 - 백준(2986번): 파스칼
  • Part 91 - 백준(3015번): 오아시스 재결합
  • Part 92 - 백준(3190번): 뱀
  • Part 93 - 백준(3190번): 벽 부수고 이동하기
  • Part 94 - 백준(4195번): 친구 네트워크
  • Part 95 - 백준(4948번): 베르트랑 공준
  • Part 96 - 백준(4963번): 섬의 개수
  • Part 97 - 백준(4991번): 로봇 청소기
  • Part 98 - 백준(5373번): 큐빙
  • Part 99 - 백준(5437번): 불
  • Part 100 - 백준(6171번): 땅따먹기
  • Part 101 - 백준(6603번): 로또
  • Part 102 - 백준(6603번): 로또
  • Part 103 - 백준(7562번): 나이트의 이동
  • Part 104 - 백준(7568번): 덩치
  • Part 105 - 백준(7576번): 토마토
  • Part 106 - 백준(7579번): 앱
  • Part 107 - 백준(7785번): 회사에 있는 사람
  • Part 108 - 백준(9012번): 괄호
  • Part 109 - 백준(9020번): 골드바흐의 추측
  • Part 110 - 백준(9184번): 신나는 함수 실행
  • Part 111 - 백준(9251번): LCS
  • Part 112 - 백준(9421번): 소수상근수
  • Part 113 - 프로그래머스: 가장 큰 정사각형 찾기
▼ 목록 보기

실버1 : 이분 탐색, Parametric Search 문제이다.

생각

처음에 이 문제를 풀 때, 세그먼트 트리로 풀려고 했다. 그런데, 문제가 생겼다. 그렇게 구현하려면, 100000개 를 50000개의 블루레이에 담으려고 한다면 계속해서 재귀 함수를 타고 들어가며 구해야 한다. 시간 제한이 2초이고, 또한 재귀함수의 depth가 너무 깊어져 이 방향으로 문제를 해결하면 안된다고 판단했다.

고민을 하던 중, 답을 먼저 제안하고, 이 답을 기반으로 역으로 추적하면 어떨까하는 생각이 들었다. 이 방법을 파라메트릭 서치(parametric Search)라 한다.

최적화 문제를 결정 문제로 바꾸어 해결한다.

이진 탐색과 비슷한 방법이다. 그런데 어떤 상황에서 사용하는지 알아야 한다. 위의 문제는 결국, N개의 입력이 들어왔을 때, 이걸 M개로 나누고, M개로 나눈 각각의 뭉텅이들 사이에서 최댓값들을 뽑아 그것의 최솟값을 구하는 문제이다. (응?)

  1. 입력 N을 M개의 집합으로 자른다. 다양한 가지수로 자를 수 있다. 이 가지수를 K라 하자.
  2. 그 집합들의 합을 갖고 있는다.
  3. 그 요소들을 가지는 집합 L를 만든다. (K의 요소 개수는 M개, 발생하는 집합 L의 개수는 K개)
  4. 발생한 모든 L 집합에 대해 요소들의 최댓값을 구한다. 그 최댓값을 모은 집합을 P라 하자. (P의 요소 개수는 K개)
  5. P집합의 요소들 중 최소값을 구한다.

완전 탐색을 한다면 위와 같이 할 수 있을 것이다. 그런데, 결국 이 문제는 최적의 디스크 크기를 구하는 문제이다. 이런 문제에서 새로운 발상을 할 수 있는데, 답은 제안하는 것이다.

방법

이 문제에서 내가 원하는 것은 disk 크기를 구하는 것이다. 이 disk 크기가 될 수 있는 최솟값과 최댓값을 계산해보자. N = 100000 이고, 각각의 동영상 용량은 10000이 최대이므로, 이 동영상 모두를 한 disk에 넣는다면 1000000000크기가 최대이다. 일단 이해를 하는 과정이기에 최댓값을 100이라 가정하겠다.

image

과정은 이분 탐색과 비슷하다. 답이 될 수 있는 최소, 최대에서 반이 답이 되는지 되지 않는지 판단한다.

image

너무 디스크 크기가 작다면 오른쪽을 탐색한다.

image

너무 디스크 크기가 크다면 왼쪽을 탐색한다.

image

위의 과정을 반복하여 답을 도출한다.

결국 위 방법의 핵심은, 원하는 답의 범위를 설정하고 이 값이 되는지 안되는지를 파악한다. 이다. 이러한 방법을 사용하기로 마음 먹었다면, 어떠한 조건에서 탐색의 방향을 바꿀 수 있는지, 해당 문제의 특징은 무엇인지 확인해야 한다.

문제의 특징

M개의 disk를 사용했을 때, disk 크기가 가장 줄어들 수 있다.

  • 만약 10개의 disk를 사용할 수 있다고 하자. 그렇다면 답은 N을 10개로 나누었을 때 가장 작게 나눌 수 있다.
  • 물론 10개보다 적은 수의 disk를 사용해서 나누었을 때 10개를 사용했을 때의 답과 같게 나올 수는 있다.
  • 따라서 10개보다 적은 수의 disk를 사용할 경우에도 답을 업데이트 해주는 대상이 된다.
  • 또한 M개의 disk를 사용할 수 있는 모든 경우의 수를 비교해야 한다. 처음에 M개로만 나뉘면 답이라 해서 틀렸다. ㅠ

탐색 조건

제시한 블루레이 Size를 가지고 만들었을 때 나온 값이 M보다 크면 Size를 키운다.

제시한 Size를 가지고 N를 최대한 나눈 결과 가지고 있는 블루레이 갯수(M)보다 disk가 더 필요하다는 결론이 나오면, 현재 disk 사이즈가 너무 작아 더 필요로 한다는 결론이다. 따라서 disk 크기를 키워야 한다.

제시한 블루레이 Size를 가지고 만들었을 때 나온 값이 M보다 작으면 지금 제시한 값을 업데이트 한다.

나온 값과 이전에 탐색한 답 중 최솟값으로 업데이트 한다.

제시하는 답은 video의 값보다는 항상 같거나 커야한다.

1 2 3 4 5 라는 video가 들어온다고 가정하자. 이 때, 내가 제시하는 답은 5보다 항상 같거나 커야 한다. 만약 3이라는 답을 제시한다면 4와 5는 disk에 들어갈 수도 없으므로 논리에서 벗어난다.

Code

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int video[100000];
int N, M, ans = 1e9;

bool isOk(int value){
    int sum = 0, count = 1;
    for (int i = 0; i < N; i++) {
        if (value < video[i]) return false;
        if (sum + video[i] > value) {
            sum = video[i];
            count++;
        }
        else sum += video[i];
        if (count > M) return false;
    }
    return true;
}

int main(){
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        cin >> video[i];
    }

    int left = 0, right = 1e9;
    while (left <= right) {
        int mid = (left+right)/2;
        if (isOk(mid)) {
            ans = min(ans, mid);
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    cout << ans << '\n';
}

Reference

백준(2343번) - 기타 레슨