본문으로 건너뛰기
김신건의 로그

레이지 프로파게이션 (Lazy Propagation)

· 수정 · 📖 약 5분 · 1,498자/단어 #algorithm #data-structure #tree #lazy-propagation
lazy propagation, 레이지 프로파게이션, 느긋한 전파, 구간 갱신, range update

정의

레이지 프로파게이션 (Lazy Propagation)세그먼트 트리의 확장으로, 구간 갱신 (range update)구간 쿼리 (range query) 를 모두 O(log N) 에 처리.

“lazy” 의 핵심: 갱신을 즉시 자식에게 전파하지 않고, 필요할 때만 (쿼리/갱신 시) 전파. 각 노드에 lazy 배열 (pending update) 을 두어 “아직 자식에게 전달 안 한 갱신” 을 캐싱.

기본 세그먼트 트리는 점 갱신 O(log N), 구간 갱신은 O(N log N). 레이지 프로파게이션으로 구간 갱신도 O(log N) 로 단축.

문제 상황과 동기

길이 N 배열에서:

  • 구간 갱신: a[l..r] += k (또는 = k)
  • 구간 쿼리: sum(l, r), min(l, r), max(l, r)
방식구간 갱신구간 쿼리비고
naiveO(N)O(N)직접 순회
[[SegtreeSegtree]] (점 갱신)O(N log N)O(log N)
Lazy PropagationO(log N)O(log N)균형

핵심 통찰: 구간 [l, r] 을 덮는 O(log N) 개 노드에만 표시. 자식은 나중에 필요할 때 계산 (lazy).

PS에서 “구간에 +k / 구간 합” 같은 갱신+쿼리 동시 요구 → 레이지 프로파게이션 필수.

시각화

핵심 아이디어

invariant: 노드 값 = 현재까지 전파된 갱신의 결과, lazy[node] = 아직 자식에게 안 보낸 갱신.

Lazy 배열:

lazy[node] = 이 노드 관할 구간에 적용할 pending 값

전파 (propagate):

if lazy[node] ≠ 0:
    tree[node] += lazy[node] * (구간 길이)
    if 리프 아님:
        lazy[2*node] += lazy[node]
        lazy[2*node+1] += lazy[node]
    lazy[node] = 0

구간 갱신 [L, R] += val:

  • 현재 노드 [l, r] 이 [L, R] 에 완전히 포함 → lazy[node] 에만 표시하고 종료.
  • 겹치지 않으면 스킵.
  • 부분 겹침 → 전파 후 왼쪽/오른쪽 재귀.

구간 쿼리 [L, R]:

  • 현재 노드 접근 전 lazy 전파.
  • 나머지는 기본 세그먼트 트리와 동일.

알고리즘

전파

propagate(node, l, r):
    if lazy[node] = 0: return
    tree[node] += lazy[node] * (r - l + 1)
    if l ≠ r:
        lazy[2*node] += lazy[node]
        lazy[2*node+1] += lazy[node]
    lazy[node] = 0

구간 갱신

range_update(node, l, r, L, R, val):
    propagate(node, l, r)
    if r < L or R < l: return
    if L ≤ l and r ≤ R:
        lazy[node] += val
        propagate(node, l, r)
        return
    mid = (l + r) / 2
    range_update(2*node, l, mid, L, R, val)
    range_update(2*node+1, mid+1, r, L, R, val)
    tree[node] = tree[2*node] + tree[2*node+1]

구간 쿼리

range_query(node, l, r, L, R):
    if r < L or R < l: return 0
    propagate(node, l, r)
    if L ≤ l and r ≤ R:
        return tree[node]
    mid = (l + r) / 2
    left = range_query(2*node, l, mid, L, R)
    right = range_query(2*node+1, mid+1, r, L, R)
    return left + right

구현

// 구간 합 + 구간 갱신 레이지 프로파게이션
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

class LazySegTree {
  vector<ll> tree, lazy;
  int n;
  
  void propagate(int node, int l, int r) {
      if (lazy[node] == 0) return;
      tree[node] += lazy[node] * (r - l + 1);
      if (l != r) {
          lazy[2*node] += lazy[node];
          lazy[2*node+1] += lazy[node];
      }
      lazy[node] = 0;
  }
  
  void build(vector<ll>& a, int node, int l, int r) {
      if (l == r) {
          tree[node] = a[l];
      } else {
          int mid = (l + r) / 2;
          build(a, 2*node, l, mid);
          build(a, 2*node+1, mid+1, r);
          tree[node] = tree[2*node] + tree[2*node+1];
      }
  }
  
  void update(int node, int l, int r, int L, int R, ll val) {
      propagate(node, l, r);
      if (r < L || R < l) return;
      if (L <= l && r <= R) {
          lazy[node] += val;
          propagate(node, l, r);
          return;
      }
      int mid = (l + r) / 2;
      update(2*node, l, mid, L, R, val);
      update(2*node+1, mid+1, r, L, R, val);
      tree[node] = tree[2*node] + tree[2*node+1];
  }
  
  ll query(int node, int l, int r, int L, int R) {
      if (r < L || R < l) return 0;
      propagate(node, l, r);
      if (L <= l && r <= R) return tree[node];
      int mid = (l + r) / 2;
      return query(2*node, l, mid, L, R) + query(2*node+1, mid+1, r, L, R);
  }
  
public:
  LazySegTree(vector<ll>& a) : n(a.size() - 1), tree(4 * n), lazy(4 * n) {
      if (n > 0) build(a, 1, 1, n);
  }
  
  void update(int l, int r, ll val) { update(1, 1, n, l, r, val); }
  ll query(int l, int r) { return query(1, 1, n, l, r); }
};

int main() {
  ios::sync_with_stdio(0); cin.tie(0);
  int n, q; cin >> n >> q;
  vector<ll> a(n + 1);
  for (int i = 1; i <= n; i++) cin >> a[i];
  
  LazySegTree seg(a);
  while (q--) {
      int t; cin >> t;
      if (t == 1) {
          int l, r; ll val; cin >> l >> r >> val;
          seg.update(l, r, val);
      } else {
          int l, r; cin >> l >> r;
          cout << seg.query(l, r) << "\n";
      }
  }
}
stdin
5 5
1 2 3 4 5
2 1 3
1 2 4 3
2 1 3
2 2 5
1 1 5 -1
결과
6
15
21

복잡도

항목
빌드O(N)
구간 쿼리O(log N)
구간 갱신O(log N)
공간O(N), 실제 4N tree + 4N lazy

각 연산이 O(log N) 개 노드만 방문. lazy 덕분에 구간 갱신도 log 시간.

변형 / 활용

1. 구간 대입 (range set)

a[l..r] = k (덮어쓰기).

// lazy 의미: "이 노드 구간 전부를 k 로"
propagate(node, l, r):
    if lazy[node] == -1: return  // -1 = no pending
    tree[node] = lazy[node] * (r - l + 1)
    if l ≠ r:
        lazy[2*node] = lazy[node]
        lazy[2*node+1] = lazy[node]
    lazy[node] = -1

초기 lazy = -1 (또는 flag).

2. Min / Max 구간 갱신

a[l..r] += k 후 구간 최솟값.

tree[node] = min(tree[2*node], tree[2*node+1]);
propagate:
    tree[node] += lazy[node];
    // min 이므로 +k 는 단순 shift

3. 2D 레이지 프로파게이션

2D 세그먼트 트리 + lazy. 구간 사각형 갱신/쿼리 O(log² N).

4. 구간 곱셈 + 구간 덧셈

두 종류 갱신 (곱 / 덧셈) 을 동시에. lazy 두 개 (mul, add).

propagate:
    tree[node] = tree[node] * lazy_mul + lazy_add * (r - l + 1)
    if l ≠ r:
        lazy_mul[2*node] *= lazy_mul[node]
        lazy_add[2*node] = lazy_add[2*node] * lazy_mul[node] + lazy_add[node]
        // 오른쪽도 동일

갱신 순서 주의: 먼저 곱, 나중에 덧셈.

함정

1. 전파 순서

range_update / range_query 진입 시 반드시 propagate 먼저. 안 하면 lazy 가 쌓여서 틀린 답.

2. 리프 여부

if (l != r) 로 자식 존재 확인. 리프에서는 자식 갱신 금지.

3. lazy 초기값

구간 덧셈: 0, 구간 대입: -1 또는 flag. 잘못 초기화하면 첫 쿼리부터 오답.

4. long long 범위

N=10^5, val=10^9, Q=10^5 구간 갱신 → 합이 매우 큼. C++ 에서 long long 필수.

5. 갱신 타입 혼용

덧셈 / 대입 / 곱셈을 동시에 쓸 때 lazy 구조 설계 복잡. 각 연산의 결합 법칙 / 교환 법칙 확인 필수.

BOJ 연습 문제

번호제목정답률링크
BOJ 10999구간 합 구하기 227.5%kokoa-lab
BOJ 12844XOR30.8%kokoa-lab
BOJ 16975수열과 쿼리 2136.2%kokoa-lab
BOJ 14427수열과 쿼리 1532.1%kokoa-lab

참고

이 글의 용어 (3개)
세그먼트 트리 (Segment Tree)algorithm
정의 세그먼트 트리 (Segment Tree) 는 배열의 구간 쿼리 (range query) 와 점 갱신 (point update) 를 모두 O(log N) 에 처리하는 이진 트…
차분 배열 (Difference Array)algorithm
정의 차분 배열 (Difference Array) 은 배열 의 인접 차 (adjacent difference) 를 저장하는 배열 (단, ). 원 배열은 , 즉 d 의 누적 합 (…
Fenwick Tree (Binary Indexed Tree): 구간 합 O(log N)algorithm
정의 Fenwick Tree (또는 BIT, Binary Indexed Tree) 는 배열의 prefix sum 을 O(log N) 에 갱신·조회 하는 자료구조입니다. Peter…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기