최장 증가 부분 수열의 응용 문제와 해결 전략

교차하지 않는 다리 건설 문제

강의 양안에 위치한 도시들을 연결하는 다리를 건설할 때 교차하지 않도록 최대 다리 수를 구하는 문제입니다. 하안 도시를 배열 인덱스로, 상안 도시 번호를 값으로 매핑하면 최장 증가 부분 수열(LIS) 문제로 변환됩니다. 도시 쌍을 정렬한 후 LIS 길이를 계산합니다.

#include <iostream>
#include <algorithm>
using namespace std;
const int MAX_N = 5005;
typedef pair<int, int> CityPair;

CityPair cities[MAX_N];
int dp[MAX_N];

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> cities[i].first >> cities[i].second;
    }
    sort(cities, cities + n);
    
    int max_bridges = 0;
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (cities[i].second > cities[j].second) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        max_bridges = max(max_bridges, dp[i]);
    }
    cout << max_bridges;
    return 0;
}

최대 합 증가 부분 수열

기본 LIS 문제를 변형하여 증가 부분 수열의 최대 합을 구합니다. 각 위치에서 이전 작은 값들의 최대 합을 누적하며 계산합니다.

int main() {
    int n;
    cin >> n;
    int arr[MAX_N], dp_sum[MAX_N];
    for (int i = 0; i < n; i++) cin >> arr[i];
    
    int max_total = 0;
    for (int i = 0; i < n; i++) {
        dp_sum[i] = arr[i];
        for (int j = 0; j < i; j++) {
            if (arr[i] > arr[j]) {
                dp_sum[i] = max(dp_sum[i], dp_sum[j] + arr[i]);
            }
        }
        max_total = max(max_total, dp_sum[i]);
    }
    cout << max_total;
    return 0;
}

트리 구조를 활용한 고속 LIS 계산

대규모 데이터 처리 시 이진 인덱스 트리(Fenwick Tree)로 최대 합 LIS를 최적화합니다. 좌표 압축을 적용해 값의 범위를 축소합니다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAX = 100010;

int arr[MAX];
LL fenwick[MAX];
vector<int> comp_vec;
LL dp_val[MAX];

int compress(int val) {
    auto it = lower_bound(comp_vec.begin(), comp_vec.end(), val);
    return distance(comp_vec.begin(), it) + 1;
}

int lowbit(int x) { return x & -x; }

void update(int idx, LL v) {
    for (int i = idx; i <= comp_vec.size(); i += lowbit(i))
        fenwick[i] = max(fenwick[i], v);
}

LL query(int idx) {
    LL ret = 0;
    for (int i = idx; i > 0; i -= lowbit(i))
        ret = max(ret, fenwick[i]);
    return ret;
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
        comp_vec.push_back(arr[i]);
    }
    sort(comp_vec.begin(), comp_vec.end());
    comp_vec.erase(unique(comp_vec.begin(), comp_vec.end()), comp_vec.end());
    
    LL result = 0;
    for (int i = 0; i < n; i++) {
        int comp_idx = compress(arr[i]);
        dp_val[i] = query(comp_idx - 1) + arr[i];
        result = max(result, dp_val[i]);
        update(comp_idx, dp_val[i]);
    }
    printf("%lld\n", result);
    return 0;
}

최소 증가 부분 수열을 이용한 시퀀스 커버

주어진 수열을 최소 개수의 증가 부분 수열로 분할하는 문제입니다. 그리디 접근법으로 각 수열의 마지막 값을 추적하며 새로운 그룹을 생성합니다.

int main() {
    int n;
    cin >> n;
    int seq[MAX], last_vals[MAX];
    for (int i = 0; i < n; i++) cin >> seq[i];
    
    int group_cnt = 0;
    for (int i = 0; i < n; i++) {
        int pos = 0;
        while (pos < group_cnt && last_vals[pos] < seq[i]) 
            pos++;
        last_vals[pos] = seq[i];
        if (pos >= group_cnt) 
            group_cnt++;
    }
    cout << group_cnt;
    return 0;
}

태그: LIS 동적계획법 이진_인덱스_트리 그리디_알고리즘 알고리즘_최적화

7월 3일 03:26에 게시됨