Problem Solving/Else

LIS 문제를 세그먼트 트리를 이용하여 풀이하는 방법

PS리버싱마크해커박종휘TV 2022. 4. 6. 17:48

제목에서 알 수 있듯이, LIS 문제를 O(N log N)의 시간복잡도로 세그먼트 트리를 이용하여 풀 수 있다.

 

 

예제로

 

9
9 5 4 6 1 3 7 10 2

 

라는 입력이 주어진다고 해보자. 여기서 LIS는 무엇일까?

LIS = { 5, 6, 7, 10 }, 혹은 { 4, 6, 7, 10 }, 혹은 { 1, 3, 7, 10 } 등으로, 길이는 4이다.

우리는 만약에 N >= 10,000 이상의 입력이 들어온다면, O(N^2)의 dp 알고리즘으로는 풀 수 없으므로,

O(N log N) 의 풀이를 알아야 한다.

 

그리고 우리는 세그먼트 트리를 활용한 구간 최댓값을 구하는 것으로 이를 풀 수 있다.

 

예제를 예로 들겠다. 배열 요소가 저장된 배열을 S라 할 때, S는 다음과 같이 표현된다.

 

S 9 5 4 6 1 3 7 10 2

 

에 대해, 오름차순 정렬을 한다. 그 배열을 R이라 하겠다.

 

R 1 2 3 4 5 6 7 9 10

 

그리고, D[i] = 1~i번 인덱스까지의 수열의 LIS 를 뜻하는 배열 D를 정의한다.

초기값은 모두 0이다.

 

D 0 0 0 0 0 0 0 0 0

 

이 세 개의 배열에 대해, N번 반복한다:

 

S 9 5 4 6 1 3 7 10 2
R 1 2 3 4 5 6 7 9 10
D 0 ...              
INDEX 0 1 2 3 4 5 6 7 8

 

i = 0일 때, R[i] = 1이고, R[i]를 배열 요소로 가지는 S 배열의 인덱스는 4이다.

S[4] = R[0] = 1 이기 때문이다. 이 인덱스를 r이라 하겠다. i = 0일때, r = 4이다.

이때 우리는 D[0~r-1] 의 최댓값 + 1이 D[r]의 값이 되는 것을 알 수 있고, 구간의 최댓값을 구하는 것이기 때문에 세그먼트 트리를 이용하여 O(log N)만에 구할 수 있다.

이때 D[0~3]의 최댓값 ( = M(D[0~3] ) 은 0이므로 D[4] = 1이다.

 

D 0 0 0 0 1 0 0 0 0
INDEX 0 1 2 3 4 5 6 7 8

 

i = 1일 때, R[i] = 2이기 때문에 r = 8이다. M(D[0~7]) = 1 (D[4]가 1으로 최댓값) 이므로 D[8] = 2이다.

 

D 0 0 0 0 1 0 0 0 2
INDEX 0 1 2 3 4 5 6 7 8

 

i = 2일 때, R[i] = 3이기 때문에 r = 5이다. M(D[0~4]) = 1 (D[4]가 1으로 최댓값) 이므로 D[5] = 2 이다.

 

D 0 0 0 0 1 2 0 0 2
INDEX 0 1 2 3 4 5 6 7 8

 

i = 3 일 때, r = 2 이다. 따라서 D[2] = 1이다.

 

D 0 0 1 0 1 2 0 0 2
INDEX 0 1 2 3 4 5 6 7 8

 

이러한 로직으로 D 배열을 채워나가보자. 마지막 단계에는 다음과 같은 테이블이 만들어진다.

이렇게 해서 D 배열은 O(N log N)만에 채워졌다.

 

D 1 1 1 2 1 2 3 4 2

 

여기서 LIS의 길이는 4라는 것을 알 수 있다. 하지만 LIS의 길이를 출력하는 것이 아닌, LIS를 출력하는 문제라면 어떻게 할까? 이는 어려울 것이라 생각되지만, 사실은 쉽다. 여기서 D 배열이 의미하는 것이 뭘까? 우리는 아까 D[i]를 정의했다: 1~i번 인덱스까지의 수열의 LIS

이는 약간 겹치는 부분이 있지 않는가? 이 문제를 DP로 풀이할 때 DP배열의 의미와 똑같다. 실제로 dp 배열을 채워보면 D 배열과 정확히 일치하는 것을 알 수 있다.

이제 문제는 한결 쉬워진다. 아니, 너무 쉬워진다. 우리는 아마 이 LIS 문제에서 LIS를 알아내보았을 것이다. 같은 로직으로 풀이하면 된다.

 

ptr = LIS의 길이 라 할 때, i = N-1 부터 i를 감소시켜나가며, ptr를 찾는 때마다 ptr를 1 감소 시키고, 스택에 S[i]를 넣는다. ptr가 0이라면 반복을 중지한다.

 

의 로직으로 풀 수 있다. 그렇다면 이 예제에서 출력은

 

4
1 3 7 10

 

이 나오고, 이는 예제의 답이라는 것을 알 수 있다.

 

이로써 LIS 문제를 세그먼트 트리로 푸는 방법을 알아보았다. 이런 문제가 과연 있을까?

놀랍게도 이 LIS 문제 에서 활용할 수 있다. 이 문제에서 유의할 점은 정렬이다. 자세한 설명은 코드를 참조.

 

난이도(객관적): Platinum V

난이도(개인적): Platinum V

 

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>

using namespace std;
#define MAXN 1000001

int N;
vector<int> S;
vector<int> D = vector<int>(MAXN);
vector<pair<int, int> > R;
vector<int> tree = vector<int>(MAXN * 4);

bool cmp(pair<int, int> left, pair<int, int> right) {
    if (left.first == right.first) return left.second > right.second;
    else return left.first < right.first;
}

int query(int node, int start, int end, int left, int right) {
    if (left > end || right < start) {
        return 0;
    }
    if (left <= start && end <= right) {
       return tree[node];
    }
    int mid = (start + end) / 2;
    int lmax = query(node * 2, start, mid, left, right);
    int rmax = query(node * 2 + 1, mid + 1, end, left, right);
    return max(lmax, rmax);
}

void update(int node, int start, int end, int idx, int val) {
    if (idx < start || idx > end) return;

    if (start != end) {
        int mid = (start + end) / 2;
        update(node * 2, start, mid, idx, val);
        update(node * 2 + 1, mid + 1, end, idx, val);
        tree[node] = max(tree[node * 2], tree[node * 2 + 1]);
    }
    else {
        tree[node] = val;
    }
}

void update(int val, int idx) {
    D[idx] = val;
    update(1, 0, N - 1, idx, val);
}

int main() {

    cin >> N;
    for (int i = 0; i < N; i++) {
        int inp; cin >> inp;
        S.push_back(inp); R.push_back( { inp, i } );
    }

    sort(R.begin(), R.end(), cmp);

    for (int i = 0; i < N; i++) {
        int v = R[i].first;
        int r = R[i].second;
        int maxi = query(1, 0, N - 1, 0, r - 1);

        update(maxi + 1, r);
    }

    int mx = 0;
    for (int i = 0; i < N; i++) {
        mx = max(D[i], mx);
    }

    cout << mx << endl;

    stack<int> ans;
    for (int i = N - 1; i >= 0; i--) {
        if (mx <= 0) break;
        if (D[i] == mx) { mx--; ans.push(S[i]); }
    }

    while (!ans.empty()) {
        cout << ans.top() << " "; ans.pop();
    }

    cout << endl;
}

 

 

'Problem Solving > Else' 카테고리의 다른 글

BOJ 5419 : 북서풍  (0) 2022.04.12
BOJ 14572 : 스터디 그룹  (0) 2022.04.08
BOJ 11570 : 환상의 듀엣  (0) 2022.04.06
BOJ 1509 : 팰린드롬 분할  (0) 2022.04.05
BOJ 1949 : 우수 마을  (0) 2022.04.03