Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
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
Tags
more
Archives
Today
Total
관리 메뉴

Elevation

트리 (3) - 느리게 갱신되는 세그먼트 트리(lazy propagation) 본문

ps/tree

트리 (3) - 느리게 갱신되는 세그먼트 트리(lazy propagation)

aste999 2026. 3. 7. 21:44

세그먼트 트리는 구간 쿼리에 특화되어 있어, 주어진 $[L, R]$ 구간의 합이나 최댓값/최솟값을 구하는 문제를 $O(\log N)$만에 잘 처리해 준다. 문제는 업데이트가 점 단위가 아닌 구간으로 주어지는 경우에 발생한다. 한 번의 점 단위 업데이트를 진행할 때 $O(\log N)$이 소요되므로, 일일이 구간 내 모든 값을 업데이트하는 것은 비효율적이다. 느리게 갱신되는 세그먼트 트리는 구간에 대한 정보를 담고 있는 세그먼트 트리의 구조를 십분 활용하여 구간 단위 업데이트를 $O(\log N)$만에 수행한다.

 

 

lazy propagation의 원리

기본 세그먼트 트리의 아이디어는 구간 쿼리를 처리할 때 구간 내에 포함되는 노드 밑으로는 더 이상 내려가지 않고 바로 결과를 반환하는 것이었다. 마찬가지로 lazy propagation의 핵심 아이디어는 구간 업데이트 요청이 들어왔을 때, 구간 내에 포함되는 노드는 해당 노드까지만 업데이트하고 나머지는 이후 필요할 때 업데이트하는 것이다.

 

이를 위해 lazy 배열을 만들어, 각 노드별로 아직 업데이트하지 않은 값인 lazy값을 관리해 준다. lazy값을 저장해 놓고, 필요할 때마다 lazy값을 원본 트리에 반영하는 push 함수를 구현하면 된다. push함수의 역할은 다음과 같다.

  • lazy[node]를 tree[node]에 반영
  • 자식 노드에 lazy[node]를 전파
  • push가 끝난 lazy[node]의 값을 초기화

 

이때 원본 tree에 lazy값을 반영하는 방식과, 자식 노드에 lazy값을 전파하는 방식은 처리하려는 쿼리에 따라 조금씩 다를 수 있다. 만약 구간 합을 관리하는 트리에, 구간 내에 동일한 값을 더하는 업데이트 요청이 들어온 경우라면 lazy값에 구간의 길이를 곱하여 더함으로써 구간 합을 tree[node]에 반영해줄 수 있다. 코드는 아래와 같다.

def _push(self, node, s, e):
    if self.lazy[node]==0: return
    self.tree[node] += self.lazy[node]*(e-s+1)
    if not s==e:
        self.lazy[node*2] += self.lazy[node]
        self.lazy[node*2+1] += self.lazy[node]
    self.lazy[node] = 0

 

 

tree[node]값을 사용하기 전에는 항상 먼저 push를 해 주어야 한다. push를 해 주어 갱신된 값을 사용해야 트리가 망가지지 않기 때문이다. 따라서 update와 query 함수의 첫 부분에서 push를 해 준다. 또한 구간 내에 노드가 포함되어 lazy값을 설정해 줄 때에도, 결국 tree[node]를 반환해 주어야 하기 때문에 한번은 push를 하고 반환할 필요가 있다.

def update(self, node, s, e, l, r, val):
    self._push(node, s, e)
    if r<s or e<l: return self.tree[node]
    if l<=s and e<=r:
        self.lazy[node] += val
        self._push(node, s, e)
        return self.tree[node]
    m = (s+e)//2
    self.tree[node] = self.update(node*2, s, m, l, r, val)+self.update(node*2+1, m+1, e, l, r, val)
    return self.tree[node]
    
def query(self, node, s, e, l, r):
    self._push(node, s, e)
    if r < s or e < l: return 0
    if l <= s and e <= r: return self.tree[node]
    m = (s+e)//2
    return self.query(node*2, s, m, l, r)+self.query(node*2+1, m+1, e, l, r)

 

 

push함수 자체는 $O(1)$에 동작하므로, 모든 깊이에서 push가 일어나더라도 트리의 총 높이인 $O(\log N)$만에 구간 업데이트 처리가 가능하다.

'ps > tree' 카테고리의 다른 글

트리 (4) - 센트로이드  (0) 2026.03.24
트리 (2) - 희소 배열, 최소 공통 조상(LCA)  (0) 2026.03.03
트리 (1) - 트리의 특성  (0) 2026.03.03