문제

 

풀이

https://www.acmicpc.net/problem/8330

이 문제와 비슷한 방식으로 접근할 수 있습니다.

 

카드 주머니가 N개 있고, 주머니 안에는 각각 1 ~ Ai가 적힌 카드가 있습니다.

여기서 연속된 K개의 카드 주머니를 고르고, 그 카드 주머니에서 카드 하나씩 꺼내서

1, 2, 3, ...., K까지 하나씩 등장하도록 만들어야 하고, 이 때 K의 최댓값을 구하는 문제입니다.

 

이 문제는 고민만 하다가 끝났었는데 Two Pointer + Segment Tree Lazy로 풀리는 문제였습니다.

트리에는 해당 숫자를 몇 번 사용할 수 있는지 카운팅을 해줍니다.

만약 N = 5라면 1은 1번, 2는 2번, 3은 3번, 4는 4번, 5는 5번 사용할 수 있게 됩니다.

주머니의 크기가 5 5 5 5 5라면 크기가 5인 주머니를 5번 사용할 수 있으며, 5 5 5 5 5 5로는 6을 만들 수 없습니다.

 

그 다음에는 l = 0, r = 0으로 시작하여 해당 범위에 속한 K개를 모두 사용하여 1 ~ K까지 하나씩 만들 수 있는지 확인합니다.

r번째 주머니를 사용한게 되기 때문에 list[r] ~ maxValue의 범위에 -1씩 카운팅을 해줍니다.

이 과정에서 트리의 값이 음수가 되었다면 1 ~ K를 고르지 못한게 됩니다.

그래서 다시 음수에서 벗어날때까지 l을 오른쪽으로 이동시켜줍니다.

트리의 값이 음수인 것을 빠르게 캐치하려면 트리에 최솟값을 저장해야 합니다.

 

5
1 1 2 5 3

예시로 설명드리면

맨 처음 트리의 상태는 1 2 3 4 5이고, 최솟값은 1입니다. (l = 0, r = 0)

 

여기서 범위를 오른쪽으로 1칸 늘립니다. (l = 0, r = 1)

값이 1이기 때문에 1 ~ 5의 카운팅을 1씩 감소시킵니다.

그러면 트리의 상태는 0 1 2 3 4가 되고, 최솟값은 0입니다.

 

다음 범위를 오른쪽으로 1칸 늘립니다. (l = 0, r = 2)

값이 1이기 때문에 1 ~ 5의 카운팅을 1씩 감소시킵니다.

그러면 트리의 상태는 -1 0 1 2 3이 되고, 최솟값은 -1입니다.

이때 음수가 되었기 때문에 l을 오른쪽으로 1칸씩 이동시킵니다. (l = 1, r = 2)

직전 l번째 수는 1이었기 때문에 1 ~ 5의 카운팅을 1씩 늘립니다.

그러면 트리의 상태는 0 1 2 3 4가 되고, 최솟값은 0이 되어 1 ~ K를 다시 만들 수 있게 됩니다.

 

다음 범위를 오른쪽으로 1칸 이동합니다. (l = 1, r = 3)

값이 2이기 때문에 2 ~ 5의 카운팅을 1씩 감소합니다.

트리의 상태를 0 0 1 2 3이 되고, 최솟값은 0이기 때문에 범위를 갱신할 수 있습니다.

 

이 과정을 끝까지 반복하여 l ~ r의 범위를 최대로 만들 수 있습니다.

 

코드

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
#include <iostream>
#include <vector>
#include <algorithm>
#define MAX 50001
using namespace std;
 
int list[MAX], tree[MAX << 2], lazy[MAX << 2];
int N, maxValue;
 
void lazyUpdate(int node, int l, int r) {
    if (!lazy[node]) return;
    tree[node] += lazy[node];
    if (l != r) {
        lazy[node << 1+= lazy[node];
        lazy[(node << 1+ 1+= lazy[node];
    }
    lazy[node] = 0;
}
 
int update(int node, int l, int r, int s, int e, int diff) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return tree[node];
    if (s <= l && r <= e) {
        tree[node] += diff;
        if (l != r) {
            lazy[node << 1+= diff;
            lazy[(node << 1+ 1+= diff;
        }
        return tree[node];
    }
 
    int m = (l + r) >> 1;
    return tree[node] = min(update(node << 1, l, m, s, e, diff), update((node << 1+ 1, m + 1, r, s, e, diff));
}
 
int query(int node, int l, int r, int s, int e) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return 1e9;
    if (s <= l && r <= e) return tree[node];
 
    int m = (l + r) >> 1;
    return min(query(node << 1, l, m, s, e), query((node << 1+ 1, m + 1, r, s, e));
}
 
void init() {
    for (int i = 1; i <= N; i++) {
        update(11, maxValue, i, maxValue, 1);
    }
}
 
void func() {
    init();
    int ret = 1;
    int l = 0;
    int r = 0;
    while (r < N) {
        update(11, maxValue, list[r], maxValue, -1);
 
        while (query(11, maxValue, list[r], maxValue) < 0) {
            update(11, maxValue, list[l], maxValue, 1);
            l++;
        }
        r++;
 
        ret = max(ret, r - l);
    }
 
    cout << ret << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
        maxValue = max(maxValue, list[i]);
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

https://www.acmicpc.net/problem/8330

 

수열이 a1, a2, ..., an 으로 주어졌을때 1 ~ ai 범위의 숫자를 적절하게 골라서 1 ~ N의 수가 하나씩 등장하도록 만들어야 합니다.

 

5
3 4 3 2 5

즉 이런 수열이 주어졌을 때

a1은 1 ~ 3 중에서 2

a2는 1 ~ 4 중에서 4

a3은 1 ~ 3 중에서 3

a4는 1 ~ 2 중에서 1

a5는 1 ~ 5 중에서 5

이렇게 골라서 1 ~ N까지 하나씩 등장하도록 순열을 만들 수 있습니다.

그리고 수열을 M번 수정하여 각각 수정된 수열에서도 위처럼 1 ~ N을 만들 수 있는지 구하는 문제입니다.

 

이 문제에서 저는 Segment Tree Lazy를 사용했고, 트리에는 해당 숫자를 몇 번 사용할 수 있는지 카운팅을 해줍니다.

1 ~ 5가 있다면

1은 1번, 2는 2번, 3은 3번, 4는 4번, 5는 5번 사용할 수 있습니다.

5 5 5 5 5가 입력된다면 5에서 수를 5번 고른다는 뜻입니다.

만약 4 4 4 4 4가 입력된다면 5개의 4에서 1 ~ 5를 하나씩 만들 수가 없게 되는거죠.

그렇게 i ~ N을 +1씩 업데이트를 해주고, 각각 노드에서의 최솟값으로 갱신하도록 합니다.

 

그 다음에는 최초로 주어진 수열에서 1 ~ N을 만들 수 있는지 먼저 확인합니다.

list[i]를 사용한게 되기 때문에 list[i] ~ N의 범위에 -1로 업데이트를 시켜줍니다.

트리를 최솟값으로 저장한 이유는 이 연산 과정에서 해당 값의 카운트가 음수인 경우를 빠르게 캐치하기 위해서입니다.

수열의 모든 수를 대상으로 업데이트가 끝난다면 query 함수를 이용하여 트리의 최솟값을 가져옵니다.

최솟값이 0 이상이면 순열을 만들 수 있고, 음수면 만들 수 없습니다.

 

다음 할 작업은 idx번째 수를 x로 변경하는 것입니다.

처음에는 위에서 -1로 업데이트를 한걸 되돌려야 하는지 고민했었는데 결과적으로 시간초과가 뜨더라고요!

N은 20만, M은 50만이기 때문에 시간초과가 당연한거였습니다.

20만개의 수를 50만번 업데이트 해야되니까요.. ㅎ

 

좀더 고민했을때는 트리에는 수열에 대한 카운팅이 각각 진행된 상태였고,

어차피 업데이트되는 idx번째 수에 대해서만 카운팅을 바꿔주면 된다는 결론이 나왔습니다.

그래서 수를 업데이트 하기 전에 기존 list[idx]의 범위에 대한 업데이트를 +1로 진행해주고,

새로운 값인 x의 범위에 대한 업데이트를 -1으로 진행해주면 새로운 수열에서의 카운팅 작업이 완료되는 것입니다.

마지막에는 업데이트된 트리의 최솟값을 가져와서 0 이상이면 `TAK`을, 음수면 `NIE`를 출력해주시면 되겠습니다.

 

 

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
#include <iostream>
#include <algorithm>
#define MAX 200001
using namespace std;
 
int list[MAX];
int tree[MAX << 2];
int lazy[MAX << 2];
int N, M;
 
void lazyUpdate(int node, int l, int r) {
    if (!lazy[node]) return;
    tree[node] += lazy[node];
    if (l != r) {
        lazy[node << 1+= lazy[node];
        lazy[(node << 1+ 1+= lazy[node];
    }
    lazy[node] = 0;
}
 
int update(int node, int l, int r, int s, int e, int diff) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return tree[node];
    if (s <= l && r <= e) {
        lazy[node] += diff;
        lazyUpdate(node, l, r);
        return tree[node];
    }
 
    int m = (l + r) >> 1;
    return tree[node] = min(update(node << 1, l, m, s, e, diff), update((node << 1+ 1, m + 1, r, s, e, diff));
}
 
int query(int node, int l, int r, int s, int e) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return 0;
    if (s <= l && r <= e) return tree[node];
 
    int m = (l + r) >> 1;
    return min(query(node << 1, l, m, s, e), query((node << 1+ 1, m + 1, r, s, e));
}
 
void init() {
    for (int i = 1; i <= N; i++) {
        update(11, N, i, N, 1);
    }
 
    for (int i = 0; i < N; i++) {
        update(11, N, list[i], N, -1);
    }
}
 
void print() {
    if (query(11, N, 1, N) >= 0cout << "TAK\n";
    else cout << "NIE\n";
}
 
void func() {
    init();
    print();
 
    int idx, x;
    while (M--) {
        cin >> idx >> x;
        update(11, N, list[idx - 1], N, 1);
        update(11, N, x, N, -1);
        list[idx - 1= x;
        print();
    }
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
    }
    cin >> M;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > SegmentTree' 카테고리의 다른 글

boj 4442 빌보의 생일  (0) 2024.08.01
boj 1517 버블 소트  (0) 2024.06.16
boj 17131 여우가 정보섬에 올라온 이유  (3) 2024.04.25
boj 12895 화려한 마을  (0) 2022.09.07
boj 10167 금광  (0) 2022.09.02

+ Recent posts