알고리즘 입문
개념 배우기직접 풀어보기
놀면 뭐 하니,
그냥 하는거야.

직접 풀어보자! 세그먼트 트리 5문제

코딩냥

안녕, 나 코딩냥이다냥! 오늘 1·2·3번은 merge 만 다른 같은 뼈대라냥. 1번을 제대로 짜두면 2·3번은 두 줄만 바꾸면 된다냥. 4·5번은 트리를 값에 세우는 새 발상이라냥.

세그먼트 트리 뼈대 (구간 합)

size = 1
while size < n:
    size *= 2
tree = [0] * (2 * size)
for i in range(n):
    tree[size + i] = a[i]
for i in range(size - 1, 0, -1):
    tree[i] = tree[2 * i] + tree[2 * i + 1]

def update(i, v):
    p = size + i
    tree[p] = v
    p >>= 1
    while p:
        tree[p] = tree[2 * p] + tree[2 * p + 1]
        p >>= 1

def query(l, r):          # l..r 포함, 0번부터
    res = 0
    l += size; r += size + 1
    while l < r:
        if l & 1: res += tree[l]; l += 1
        if r & 1: r -= 1; res += tree[r]
        l >>= 1; r >>= 1
    return res
미션 1·바뀌는 구간 합25분

값을 바꾸며 구간 합을 구한다냥.

문제 풀러 가기

14장 누적합으로 풀면 틀린다냥.

누적합을 한 번 만들어두고 값이 바뀔 때 안 고치면, 갱신 이후의 구간 합이 어긋난다냥. 값이 바뀌니 세그먼트 트리가 필요하다냥. 숨겨진 테스트에 갱신이 섞여 있어서 이걸 잡는다냥.

위 뼈대를 그대로 쓰면 된다냥. 입력의 위치는 1번부터니, update 와 query 에 넘길 때 1 을 빼서 0번부터로 맞추자냥.

미션 2·바뀌는 구간 최솟값15분

값을 바꾸며 구간 최솟값을 구한다냥.

문제 풀러 가기

1번에서 두 곳만 바꾼다냥.

  • 합치는 방법: + → min
  • 항등원(빈 값): 0 → 무한대
INF = float("inf")
tree = [INF] * (2 * size)
# tree[p] = min(tree[2*p], tree[2*p+1])
# query 의 res 도 INF 로 시작, res = min(res, ...)

항등원을 0 으로 두면 최솟값이 항상 0 이하로 오염되니, 무한대로 맞추는 게 중요하다냥.

미션 3·바뀌는 구간 최댓값10분

값을 바꾸며 구간 최댓값을 구한다냥.

문제 풀러 가기

이번엔 merge = max, 항등원 -무한대 라냥. 2번에서 min 을 max 로, 무한대 를 -무한대 로 바꾸면 끝이라냥.

세 문제가 같은 뼈대에서 합치는 방법과 항등원만 다르다는 걸 확인하자냥.

미션 4·k번째 원소 꺼내기25분

현재 원소 중 k번째로 작은 값을 출력하고 꺼낸다냥.

문제 풀러 가기

뼈대값 공간 개수 트리 + 내려가기라냥

값 1..M 의 개수를 담은 트리를 만든다냥. 넣기는 그 값의 잎에 +1, 꺼내기는 k번째 값을 찾아 -1 이라냥.

def kth(k):
    node = 1
    while node < size:
        left = tree[2 * node]
        if k <= left:
            node = 2 * node
        else:
            k -= left
            node = 2 * node + 1
    return node - size

루트에서 왼쪽 개수와 k 를 비교하며 내려가면 O(log M) 에 찾는다냥.

찾은 뒤에 그 값을 꺼내야 한다냥.

2 k 연산은 k번째 값을 출력하고 제거한다냥. kth(k) 로 값을 찾은 다음, 그 값의 개수를 1 줄이는 갱신을 잊지 말자냥. 공개된 예시에서 같은 값을 여러 번 꺼내는 경우를 확인하자냥.

미션 5·x 이하가 몇 개15분

현재 원소 중 값이 x 이하인 것의 개수를 센다냥.

문제 풀러 가기

4번과 같은 개수 트리라냥. 이번엔 k번째 내려가기 대신 값 1..x 의 구간 합 을 구하면 된다냥.

# 넣기: 그 값의 잎에 +1
# 질의: sum_to(x) = 값 1..x 개수의 합

트리는 4번과 똑같고, 하는 연산만 "구간 합 질의" 로 바뀐다냥. 이게 값 공간 트리의 유연함이라냥.

코딩냥

다섯 개 다 풀었냥? 오늘로 자료구조와 알고리즘 한 바퀴를 돌았다냥. 세그먼트 트리는 "값이 바뀌는 구간 질의" 의 만능 열쇠라냥.

다음은 마지막, 실전 종합이라냥. 지금까지 배운 걸 섞어서, 문제를 보고 어떤 기법을 꺼낼지 고르는 눈을 기른다냥. 정말 수고했다냥~