개념

그리디는 각 단계에서 정한 기준에 따라 하나를 선택하고, 그 선택을 유지하며 답을 만드는 방법입니다. 최적해는 문제에서 요구하는 값을 가장 크게 또는 가장 작게 만드는 답입니다. 그리디를 사용하려면 지금의 선택을 포함하는 최적해가 있다는 근거가 필요합니다.

문제 유형에 따른 선택 기준

문제 유형구하는 결과선택·처리 기준확인할 근거
구간 선택겹치지 않는 구간의 최대 개수종료 시각이 빠른 순서이후에 고를 구간을 방해하지 않는지
구간 병합겹치는 구간을 합친 결과시작점이 작은 순서현재 구간이 끝났다고 확정할 수 있는지
처리 순서 결정완료 시각의 합의 최솟값처리 시간이 짧은 순서인접한 두 작업을 바꿀 때 합이 줄어드는지
동전 선택목표 금액을 만드는 최소 동전 수큰 금액부터 선택하는 기준 검토동전 단위 사이에 교환이 가능한지

유형이 같아도 조건이나 목적이 바뀌면 기준이 달라집니다. 구간의 개수를 최대화하는 문제와 구간마다 주어진 보상의 합을 최대화하는 문제는 구별해야 합니다. 보상이 있다면 늦게 끝나는 구간 하나의 보상이 짧은 구간 여러 개의 보상보다 클 수 있습니다.

동전도 큰 것부터 고르는 방법이 항상 성립하지는 않습니다. 단위가 11, 33, 44이고 목표 금액이 66이면 큰 동전부터 고를 때 4+1+14+1+1로 33개, 3+33+3으로 만들 때는 22개입니다. 반면 11, 55, 1010처럼 각 단위가 바로 아래 단위의 정수배이면 작은 동전 여러 개를 큰 동전 하나로 바꾸어 개수를 줄일 수 있습니다. 이 경우 큰 단위부터 선택하는 근거가 됩니다.

종료 시각이 빠른 구간 선택

한 장소에서 활동을 겹치지 않게 진행하며 최대한 많이 선택하려고 합니다. 가장 빨리 끝나는 활동을 고르면 다음 활동을 시작할 수 있는 시각을 가장 앞당길 수 있습니다.

종료 시각이 빠른 순서로 정렬한 뒤, 직전에 고른 활동의 종료 시각 이상에서 시작하는 활동을 선택합니다. 선택했다면 마지막 종료 시각을 갱신합니다.

활동이 (1,10)(1,10), (2,3)(2,3), (3,4)(3,4), (5,7)(5,7)이면 종료 시각 순서는 (2,3)(2,3), (3,4)(3,4), (5,7)(5,7), (1,10)(1,10)입니다. 앞의 세 활동은 순서대로 선택할 수 있어 답이 33입니다. 시작 시각만 보고 (1,10)(1,10)을 먼저 고르면 한 개만 선택하게 됩니다.

첫 선택을 바꾸어도 되는 이유

활동을 최대한 많이 선택한 어떤 최적해를 잡고, 그 첫 활동을 AA라고 합니다. 전체 활동 중 가장 빨리 끝나는 활동을 GG라고 하면, GG의 종료 시각은 AA의 종료 시각 이하입니다.

최적해에서 AA를 GG로 바꾸어도 이후 활동은 그대로 진행할 수 있습니다. 이후 활동은 원래 AA가 끝난 뒤 시작하므로, 더 일찍 또는 같은 시각에 끝나는 GG와도 겹치지 않기 때문입니다. 선택한 개수도 같습니다. 따라서 GG를 첫 활동으로 포함하는 최적해가 존재합니다.

GG와 겹치는 활동을 제외하면 남은 문제도 같은 형태입니다. 남은 활동에서 다시 가장 빨리 끝나는 것을 선택하는 과정을 반복할 수 있습니다. 이처럼 최적해의 선택을 그리디의 선택으로 바꿔도 답이 나빠지지 않음을 보이는 방법이 교환 논증입니다.

복잡도

활동 수를 NN이라고 합니다.

연산시간복잡도공간복잡도
정렬을 포함한 구간 선택O(Nlog⁡N)O(N\log N)입력 저장 O(N)O(N)
정렬된 구간 확인O(N)O(N)추가 공간 O(1)O(1)

예제

문제

활동의 시작 시각과 종료 시각이 주어진다. 겹치지 않게 선택할 수 있는 활동의 최대 개수를 구하여라. 앞 활동의 종료 시각에 다음 활동을 시작할 수 있다.

입력

첫째 줄에 활동 수 NN이 주어진다. 1≤N≤1001\le N\le100이다.

다음 NN개 줄에 활동의 시작 시각 ss와 종료 시각 ee가 공백을 사이에 두고 주어진다. 1≤s<e≤1 0001\le s<e\le1\,000이다.

출력

선택할 수 있는 활동의 최대 개수를 출력한다.

예제 입력

4
1 3
2 5
3 4
7 8

예제 출력

3

예제 코드

C++20

#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>

using namespace std;

bool compare(const pair<int, int>& a, const pair<int, int>& b) {
    // 종료 시각 오름차순
    return a.second < b.second;
}

vector<pair<int, int>> v;

int main() {
    int n, i, cnt, end;

    cin >> n;
    v.resize(n);
    for (i = 0; i < n; ++i) {
        cin >> v[i].first >> v[i].second;
    }

    sort(v.begin(), v.end(), compare);
    cnt = 0;
    end = 0;
    for (const auto& [s, e] : v) {
        if (s >= end) {
            ++cnt;
            end = e;
        }
    }

    cout << cnt << '\n';
}