2 minute read

문제 링크 :

https://www.acmicpc.net/problem/2805

몸이 안 좋아도 블로그는 정상 영업합니다……..

매개 변수 탐색(=파라메트릭 서치, Parametric Search) 문제이다.

Parametric Search란?

최적화 문제를 결정 문제로 바꾸어 푸는 것

최적화 문제: 조건을 만족하는 어떤 변수의 최솟값 또는 최댓값을 구하는 문제

이런 최적화 문제를 결정 문제로 바꾸어 푸는 것이 parametric search이다.

예를들면, 2805 나무자르기 문제에서 이진탐색을 하면서 mid만큼의 높이로 잘랐을 때, 그 잘린 나무 길이의 합이 필요한 나무 길이를 충족시켜주는지 결정해주는 함수를 만들면 된다.

bool can_make(ll M, ll mid) {
    ll total = 0;
    for(int h: height) {
        if(h >= mid) total += h-mid;
    }

    if(total >= M) return true;
    else return false;
}

can_make의 반환 값이 true → 톱의 높이를 좀 더 올려서 그 때에도 조건이 충족되는지 확인한다. 즉, mid값을 저장해놓고 left = mid+1로 바꾸어 이진 탐색을 계속한다.

can_make의 반환 값이 false → 톱의 높이를 낮추어야한다. right = mid -1로 바꾸어 이진탐색을 계속한다.

입력이

4 7

20 15 10 17

일 때

0705_1

이진탐색의 범위를 left = 0, right = (최대 나무 길이)로 했기 때문에 처음 mid 값은 (20+0) / 2 = 10이다.

톱 위치가 10일 때, 잘리는 나무의 길이는 그림과 같이 22이므로, 7보다 커서 조건을 충족한다.

그럼 톱의 위치를 올려서 이 조건을 충족하면서 더 큰 값이 있는지 찾아야한다.

따라서 left = mid+1로 바꾼다.

0705_2

left = 11이 되었으므로, 두 번째 mid는 (11+20)/2 = 15이다.

톱 높이가 15일 때, 잘리는 나무 길이는 그림과 같이 7이므로 조건을 충족하고, 15가 정답이다.

다음 반복 때 left = 16, right = 20인 상태로 이진탐색이 더 진행되겠지만, 앞으로는 결정해주는 함수가 fasle만 리턴할 것이고, right의 값이 작아지다가 left>right가 되면서 반복문이 종료될 것이다.

정리하자면

can_make의 리턴값에 의해 범위를 좁혀가며 이진탐색을 계속하면, 조건을 만족하면서 톱 높이의 최댓값을 구할 수 있다.

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

typedef long long ll;
vector<ll> height;

bool can_make(ll M, ll mid) {
    ll total = 0;
    for(int h: height) {
        if(h >= mid) total += h-mid;
    }

    if(total >= M) return true;
    else return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    int N;
    ll M;

    cin >> N >> M;

    for(int i=0; i<N; ++i) {
        ll tmp;
        cin >> tmp;
        height.push_back(tmp);
    }

    ll left = 0;
    ll right = (ll)(*max_element(height.begin(), height.end()));
    ll ans = 0;
    while(left <= right) {
        ll mid = (left + right) / 2;

        if(can_make(M, mid)) {
            ans = mid;
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    cout << ans << '\n';
    return 0;
}

참고

처음에 ‘틀렸습니다’가 떠서 당황했는데, 알고보니 톱 위치가 0이 될 수도 있다는 것을 까먹고 left = 1로 이진탐색을 시작해서 그랬던거였다..

ans도 -1로 초기화했었고 left도 1로 시작했었는데, 이렇게되면

3 3

1 1 1

과 같은 테케를 넣으면 정답 값 0 대신에 -1(ans의 초기화값)이 출력된다.

left = 0으로 시작하면 해결된다. (아니면 ans = 0으로 초기화해도 해결된다.)

Leave a comment