백준16232 없던 일처럼

문제 링크

  • http://icpc.me/16232

사용 알고리즘

  • 세그먼트 트리

시간복잡도

  • $O(N \log^2 N)$

풀이

$A_i \leq B_i$이기 때문에 $N$개의 사건에 의한 멘탈 변화량은 $N+1$가지입니다. 가능한 케이스가 $2^N$이 아닌 $N+1$라는 점을 이용해 문제의 풀이를 고민해볼 수 있습니다.

각 사건을 두 순서쌍 $(-\infty, A_i), (K_i, B_i)$으로 표현합니다. 이는 해당 사건 이전의 멘탈이 $K_i$ 이상이라면 $B_i$만큼 변하고, $-\infty$ 이상이라면 $A_i$만큼 변한다는 것을 의미합니다.
각 사건을 두 순서쌍으로 표현한다면, 사건 $(-\infty, A_i), (K_i, B_i)$와 $(-\infty, A_j), (K_j, B_j)$를 연달아 거치는 것은 세 개의 순서쌍 $(-\infty, A_i+A_j)$, $(K_i, B_i + A_j)$, $(K_j, A_j + B_j)$로 표현할 수 있습니다. ($K_i \leq K_j$)

이를 이용해 연속한 몇 개의 사건을 연달아 거치는 경우를 세그먼트 트리를 이용해 구할 수 있습니다. 세그먼트 트리의 리프 정점에서 각 사건을 관리하고, 내부 정점의 값은 Merge Sort Tree처럼 두 자식을 합치는 것으로 구할 수 있습니다.
세그먼트 트리를 전처리했다면, 사건 $i$가 없었을 때의 최종 상태를 구하는 것은 $\text{Query}(1, i-1)$과 $\text{Query}(i+1, N)$을 합친 결과가 됩니다.

$\text{Query}(l, r)$함수를 구현하는 것은, 구간 $[l, r]$을 나타내는 세그먼트 트리의 정점을 모두 구한 뒤, 차례대로 upper_bound 등의 함수를 이용해 멘탈 변화를 반영하면 됩니다.

전체 코드

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
/******************************
Author: jhnah917(Justice_Hui)
g++ -std=c++17 -DLOCAL
******************************/

#include <bits/stdc++.h>
#define x first
#define y second
#define all(v) v.begin(), v.end()
#define compress(v) sort(all(v)), v.erase(unique(all(v)), v.end())
#define IDX(v, x) (lower_bound(all(v), x) - v.begin())
using namespace std;

using uint = unsigned;
using ll = long long;
using ull = unsigned long long;
using PLL = pair<ll, ll>;
constexpr int S = 1 << 18;
constexpr ll INF = 1e15;

int N;
ll A[202020], B[202020], K[202020];
vector<PLL> T[1 << 19];

vector<PLL> Merge(const vector<PLL> &l, const vector<PLL> &r){
    if(r.empty()) return l; if(l.empty()) return r;
    vector<PLL> ret(1, PLL(-INF,  l[0].y + r[0].y));

    for(int i=1, j=0; i<l.size(); i++){
        while(j+1 < r.size() && l[i].x + l[i].y >= r[j+1].x) j++;
        ret.emplace_back(l[i].x, l[i].y + r[j].y);
    }

    for(int i=0, j=1; j<r.size(); j++){
        while(i+1 < l.size() && l[i+1].x + l[i+1].y <= r[j].x) i++;
        if(i+1 < l.size() && r[j].x - l[i].y >= l[i+1].x) continue;
        if(j+1 < r.size() && l[i].x + l[i].y >= r[j+1].x) continue;
        ret.emplace_back(r[j].x - l[i].y, l[i].y + r[j].y);
    }
    compress(ret);
    return ret;
}

void Build(){
    for(int i=1; i<=N; i++){
        T[i|S].emplace_back(-INF, A[i]);
        T[i|S].emplace_back(K[i], B[i]);
    }
    for(int i=S-1; i; i--) T[i] = Merge(T[i << 1], T[i << 1 | 1]);
}

vector<int> Gather(int l, int r){
    l |= S; r |= S;
    vector<int> lv, rv;
    while(l <= r){
        if(l & 1) lv.push_back(l++);
        if(~r & 1) rv.push_back(r--);
        l >>= 1; r >>= 1;
    }
    reverse(all(rv));
    for(auto i : rv) lv.push_back(i);
    return lv;
}

ll Query(int l, int r, ll v){
    auto vec = Gather(l, r);
    for(auto nd : vec){
        auto it = upper_bound(all(T[nd]), PLL(v, INF));
        v += it == T[nd].begin() ? 0 : prev(it)->y;
    }
    return v;
}

int main(){
    ios_base::sync_with_stdio(false); cin.tie(nullptr);
    cin >> N; for(int i=1; i<=N; i++) cin >> A[i] >> B[i] >> K[i];
    Build();

    for(int i=1; i<=N; i++){
        ll t1 = Query(0, i-1, 0);
        ll t2 = Query(i+1, N+1, t1);
        cout << t2 << "\n";
    }
}