Featured image of post [Algorithm] C++ 백준 18855번 : Treatment Project

[Algorithm] C++ 백준 18855번 : Treatment Project

JOI 2019/2020 Day4 C, 백준 18855 Treatment Project의 정해 구현. 프로젝트들을 정점(정점 비용)으로 보고 시간-구간 제약을 불등식으로 변환해 간선을 정의, 시작(L=1)에서 종료(R=N)까지 정점 비용 최단경로를 다익스트라로 구한다. 간선 전개는 T별로 정렬해 세그트리로 한 번씩만 활성화하여 전체 O(M log M)에 해결한다.

문제: BOJ 18855 - Treatment Project

아이디어 요약

  • 모든 시민이 회복하려면 시작 경계 1과 끝 경계 N을 반드시 한 번씩 덮는 프로젝트가 필요합니다. 각 프로젝트 i를 정점(선택 비용 C_i)으로 보고, “감염 경계선”이 시간에 따라 이동할 수 있게 만드는 다음 불등식으로 간선을 정의합니다.
    • T_i ≤ T_j이면 L_j + T_j ≤ R_i + T_i (우측으로 확장 가능)
    • T_i ≥ T_j이면 L_j − T_j ≤ R_i − T_i (좌측으로 확장 가능)
  • 시작 정점: L=1(코드에서 L←L−1로 half-open 처리 후 L=0)인 프로젝트. 종료 정점: R=N인 프로젝트. 시작들에서 종료들까지의 “정점 비용” 최단경로가 최소 합계 비용.
  • 간선 전개 최적화: valF=L+T, valB=L−T, thF=R+T, thB=R−T를 두고 T별로 valF/valB로 정렬해 두 그룹을 만든 뒤, 다익스트라 진행 중 세그트리로 “현재 한계(thF/thB) 이하의 머리 원소”만 한 번씩 꺼내 활성화합니다. 각 정점은 많아야 한 번 활성화되어 전체 O(M log M).

구현 메모

  • 입력은 1-index [L, R]라서 L ← L-1로 half-open 처리합니다(불등식이 깔끔해짐).
  • long long으로 안전하게 합산합니다(C_i ≤ 1e9, M ≤ 1e5).
  • 시작 정점(Seed)은 우선순위 큐에 비용 C_i로 넣고, 그룹 활성화 목록에서는 제외해도 됩니다(시작을 중간에 다시 도달하는 것이 이득이 아님).

C++ 풀이

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 더 많은 알고리즘/풀이 글은 42jerrykim.github.io에서 확인하세요.
#include <bits/stdc++.h>
using namespace std;

using int64 = long long;
const int64 INF = (1LL<<62);

struct Project {
    int64 T, L, R, C; // L is shifted later (L-1)
    int tpos;         // compressed T index
    int64 valF;       // L + T
    int64 valB;       // L - T
    int64 thF;        // R + T
    int64 thB;        // R - T
};

struct SegMin {
    int n; vector<int64> tree;
    SegMin() {}
    SegMin(int sz, const vector<int64>& init) { build(sz, init); }
    void build(int sz, const vector<int64>& init) {
        n = 1; while (n < sz) n <<= 1;
        tree.assign(2*n, INF);
        for (int i = 0; i < (int)init.size(); ++i) tree[n+i] = init[i];
        for (int i = n-1; i >= 1; --i) tree[i] = min(tree[i<<1], tree[i<<1|1]);
    }
    void point_set(int pos, int64 val) {
        int p = pos + n; tree[p] = val;
        for (p >>= 1; p; p >>= 1) tree[p] = min(tree[p<<1], tree[p<<1|1]);
    }
    int find_first_le(int l, int r, int64 limit) { return find_first_le_impl(1, 0, n-1, l, r, limit); }
    int find_first_le_impl(int node, int nl, int nr, int ql, int qr, int64 limit) {
        if (qr < nl || nr < ql) return -1;
        if (tree[node] > limit) return -1;
        if (nl == nr) return nl;
        int mid = (nl + nr) >> 1;
        int left = find_first_le_impl(node<<1, nl, mid, ql, qr, limit);
        if (left != -1) return left;
        return find_first_le_impl(node<<1|1, mid+1, nr, ql, qr, limit);
    }
};

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

    int64 N; int M;
    if (!(cin >> N >> M)) return 0;

    vector<Project> a(M);
    vector<int64> allT; allT.reserve(M);
    for (int i = 0; i < M; ++i) {
        cin >> a[i].T >> a[i].L >> a[i].R >> a[i].C;
        a[i].L -= 1; // make half-open
        allT.push_back(a[i].T);
    }

    sort(allT.begin(), allT.end());
    allT.erase(unique(allT.begin(), allT.end()), allT.end());
    for (int i = 0; i < M; ++i) {
        a[i].tpos = int(lower_bound(allT.begin(), allT.end(), a[i].T) - allT.begin());
        a[i].valF = a[i].L + a[i].T;
        a[i].valB = a[i].L - a[i].T;
        a[i].thF = a[i].R + a[i].T;
        a[i].thB = a[i].R - a[i].T;
    }

    int Tcnt = (int)allT.size();
    vector<vector<int>> grpF(Tcnt), grpB(Tcnt);
    vector<int> ptrF(Tcnt, 0), ptrB(Tcnt, 0);

    vector<int> seeds, exitNodes;
    seeds.reserve(M); exitNodes.reserve(M);
    for (int i = 0; i < M; ++i) {
        if (a[i].L == 0) seeds.push_back(i);
        else {
            grpF[a[i].tpos].push_back(i);
            grpB[a[i].tpos].push_back(i);
        }
        if (a[i].R == N) exitNodes.push_back(i);
    }

    if (seeds.empty() || exitNodes.empty()) {
        cout << -1 << '\n';
        return 0;
    }

    for (int t = 0; t < Tcnt; ++t) {
        sort(grpF[t].begin(), grpF[t].end(), [&](int i, int j){ return a[i].valF < a[j].valF; });
        sort(grpB[t].begin(), grpB[t].end(), [&](int i, int j){ return a[i].valB < a[j].valB; });
    }

    vector<int64> headF(Tcnt, INF), headB(Tcnt, INF);
    for (int t = 0; t < Tcnt; ++t) {
        if (!grpF[t].empty()) headF[t] = a[grpF[t][0]].valF;
        if (!grpB[t].empty()) headB[t] = a[grpB[t][0]].valB;
    }
    SegMin segF(Tcnt, headF), segB(Tcnt, headB);

    vector<int64> dist(M, INF);
    priority_queue<pair<int64,int>, vector<pair<int64,int>>, greater<pair<int64,int>>> pq;
    for (int i : seeds) { dist[i] = a[i].C; pq.push({dist[i], i}); }

    while (!pq.empty()) {
        auto [d, i] = pq.top(); pq.pop();
        if (d != dist[i]) continue;

        int tpos = a[i].tpos;

        // T_j >= T_i, activate by valF <= thF
        {
            int64 limit = a[i].thF;
            while (true) {
                int pos = segF.find_first_le(tpos, Tcnt-1, limit);
                if (pos == -1) break;
                int j = grpF[pos][ptrF[pos]++];
                int64 newHead = (ptrF[pos] < (int)grpF[pos].size()) ? a[grpF[pos][ptrF[pos]]].valF : INF;
                segF.point_set(pos, newHead);
                int64 nd = d + a[j].C;
                if (nd < dist[j]) { dist[j] = nd; pq.push({nd, j}); }
            }
        }

        // T_j <= T_i, activate by valB <= thB
        {
            int64 limit = a[i].thB;
            while (true) {
                int pos = segB.find_first_le(0, tpos, limit);
                if (pos == -1) break;
                int j = grpB[pos][ptrB[pos]++];
                int64 newHead = (ptrB[pos] < (int)grpB[pos].size()) ? a[grpB[pos][ptrB[pos]]].valB : INF;
                segB.point_set(pos, newHead);
                int64 nd = d + a[j].C;
                if (nd < dist[j]) { dist[j] = nd; pq.push({nd, j}); }
            }
        }
    }

    int64 ans = INF;
    for (int j : exitNodes) ans = min(ans, dist[j]);
    cout << (ans >= INF/2 ? -1 : ans) << '\n';
    return 0;
}

복잡도

  • 전개/완화는 각 정점당 한 번씩, 세그트리/우선순위 큐 연산 O(log M) → 전체 O(M log M)
  • 메모리 O(M)

참고