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

  • 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 - 백준(2343번): 기타 레슨
  • 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 - This Post
  • 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 : 동적 계획법, Convex Hull 문제이다.

생각

일단 완전 탐색을 생각해 본다. 음.. 말도 안된다. 몇개를 겹쳐서 만드는지 완전탐색을 한다면 시간 복잡도가 $O(n!)$ 이다. 이 문제는 그냥 직관적으로 동적 계획법 냄새가 난다. 어떠한 조건에서 특정 위치까지의 땅의 최소값이 다음을 구하는데 영향을 줄 것 같기 때문이다.

정의

dp[n] = n번째 땅까지 구매했을 때 비용의 최솟값

이렇게 정의했다면, 이제는 점화식을 어떻게 세울지 고민해야 한다. n번째 땅까지 구매했을 때 비교해봐야 하는 값들을 나열해 보자.

  1. n번째 땅을 그냥 산다. 그리고 n-1까지의 최소값과 더한다.
  2. n-1번째 땅과 묶어서 계산해본다. 그리고 n-2까지의 최솟값과 더한다.
  3. n-2번째 땅과 묶어서 계산해본다. 그리고 n-3까지의 최솟값과 더한다.

호오. 가능할 것 같은 냄새가 난다. 이렇게 할 경우 $O(n^2)$ 까지 줄일 수 있을 것 같다.

\[dp[i] = min_{j=i}^{j=1}(dp[j-1] + \sum_{k=j}^{k=i}a_k)\]
dp[i] = min(dp[j-1] + j부터 i까지의 땅을 합쳐 산 금액)

이 것을 수식으로 정리해보면, 위와 같다. 그렇다면 j부터 i가지의 땅을 합쳐 산 금액은 어떻게 구할까?

j부터 i가지의 땅을 합쳐 산 금액

실제로 땅을 사본다고 생각한다. 가장 빨리 직관을 얻는 것은 그림을 그려보는 것이다. 결국 이 문제는, 땅을 구입하는데 있어서 이걸 통짜로 한번에 사버린다는 얘기와 같다.

image

입력이 100 1, 20 20이 들어왔다고 생각하자. 이 부분에 해당하는 땅을 그리면 위와 같다. 그런데, 내가 이 두 땅을 합친다고 생각하면 결국 중요한 것을 최대 너비와 최대 높이이다. 아까 위에서 본 것처럼 결국 dp[i]을 구하기 위해서는 맨 아래 땅부터 포함해서 위쪽 땅을 1개, 2개 3개 … 를 선택하여 합쳐야 한다. 그렇다면, 땅을 합치는 데 있어서 3개를 합친다면, 3개의 땅의 너비와 높이를 다 비교해서 얻어내야 하는데.. 굳이 그래야 할까?

정렬하기

굳이 그럴 필요 없다. dp[i]을 구하는데 있어서 무조건 포함해야 하는 땅은 i번째 땅이다. 그렇다면 내가 합치고 싶은 범위의 땅은 j번째 땅이다. 이 두땅을 각각 ground[i], ground[j] 라 하겠다.

땅을 구하는데 있어서 위의 문제처럼 일일히 최댓값을 구하지말고, 이렇게 하면 좋겠다. image

ground[i] = 합칠 땅의 최대 height

ground[j] = 합칠 땅의 최대 width

이러면, 시간 복잡도 $O(1)$에 가능하다..!

필요없는 땅 제거

그런데 정렬을 하다보니 이런 땅도 있다.

image

음.. 이런 땅은 어쩌지? 잘 생각해보면, 이 땅은 필요없다. 이미 땅을 살 때, 포함되는 땅이기 때문이다. 예를 들어 20 20을 구매한다면, 15 15는 합쳐살 경우 자연스레 구매하게 되는 땅이다. 합쳐사지 않는 다면 오히려 비용만 늘기 때문에 결국 이 땅은 최소값을 구하는데 도움이 되지 않는 땅이다. 또한 이 땅을 배열에 갖고 있게 된다면, 우리가 위에서 만들고 싶은 정렬 규칙에 위배되므로써 상수시간 안에 구하는 것이 어려워진다. 따라서 이런 조건에 해당하는 땅은 제거한다.

정렬한 땅에서, 지금 땅의 높이보다 다음 땅의 높이가 크면 추가한다.

점화식

dp[i] = min(dp[j-1] + (ground[j].w * ground[i].h)) > (단, i >= j >= 1)

Code

#include<iostream>
#include<vector>
#include<string>
#include <algorithm>
using namespace std;

typedef long long ll;
struct Square {
    ll w, h;
};

int N;
vector<Square> temp;
vector<Square> ground;
vector<ll> dp;

bool compare(Square a, Square b){
    if (a.w == b.w) return a.h < b.h;
    return a.w > b.w;
}

int main(){
    cin >> N;
    temp.resize(N);
    for (int i = 0; i < N; i++) {
        cin >> temp[i].w >> temp[i].h;
    }
    sort(temp.begin(), temp.end(), compare);

    ground.push_back(temp[0]);
    ground.push_back(temp[0]);
    for (int i = 1; i < N; i++) {
        Square next = temp[i];
        Square now = ground.back();
        if (next.h > now.h) ground.push_back(next);
    }

    int size = int(ground.size());
    dp.resize(size);

    for (int i = 1; i < size; i++) {
        dp[i] = dp[i-1] + ground[i].w * ground[i].h;
        for (int j = i; j > 0; j--) {
            dp[i] = min(dp[i], dp[j-1] + (ground[j].w * ground[i].h));
        }
    }
    cout << dp[size-1] << '\n';
    return 0;
}

Convex Hull

하지만.. 플레티넘의 위엄이 있다. 이렇게 풀면 시간초과가 난다. 당연히 입력이 1000000이기 때문에 최악의 경우 $O(n^2)$ 이다. 그래서 여기서는 추가적인 최적화가 필요하다.

Reference

백준(6171번) - 땅따먹기