왜 게으름이 필요한가
일반 세그먼트 트리는 점 갱신과 구간 질의를 각각 \(O(\log N)\)에 처리한다.
그런데 "구간 \([l, r]\)의 모든 원소에 \(v\)를 더하라" 같은 구간 갱신을
정직하게 처리하면 구간 길이만큼의 리프를 건드려야 해서 한 번에 \(O(N)\)이 든다.
핵심 아이디어는 "지금 당장 자손 전부를 갱신하지 말고, 갱신해야 한다는 사실만
노드에 메모해 두자" 이다. 이 메모를 lazy 값이라 부르고, 실제 내려보내는
일을 나중으로 미루는 것을 게으른 전파(lazy propagation) 라 한다.
불변식
각 노드는 두 정보를 든다.
tree[x]: 이 노드가 담당하는 구간 전체에 lazy가 이미 반영된 집계값.lazy[x]: 이 노드의 자식들에게 아직 내려보내지 않은 보류 갱신.
여기서 가장 중요한 불변식은 다음과 같다.
tree[x]는 자기 자신의 lazy까지 모두 반영한 정답이다. 다만lazy[x]는
자식에게는 아직 적용되지 않았다.
따라서 어떤 노드의 자식으로 내려가기 직전에만 lazy를 한 단계 밀어내면
(push_down), 항상 정확한 값을 보게 된다.
push_down 과정
구간 더하기 + 구간 합 질의를 예로 들면, 노드 \(x\)가 길이 \(len\) 구간을 담당할 때
"구간 전체에 \(v\) 더하기"는 합에 \(v \times len\)을 더하는 것과 같다.
| 연산 | tree 갱신 | lazy 누적 |
|---|---|---|
| 구간에 \(v\) 더하기 | tree[x] += v * len |
lazy[x] += v |
| 자식으로 전파 | tree[child] += lazy[x] * len_child |
lazy[child] += lazy[x] |
전파가 끝나면 lazy[x] = 0으로 비운다. 더하기는 교환·결합법칙이 성립하므로
보류된 갱신을 누적해 두었다가 한꺼번에 적용해도 결과가 같다. 이 성질이 lazy
전파가 성립하는 근본 이유다.
정당성과 복잡도
구간 갱신과 질의 모두 표준 세그먼트 트리처럼 완전히 덮이는 노드에서 멈춘다.
이런 노드는 깊이마다 최대 4개뿐이므로 방문 노드 수는 \(O(\log N)\)이고,
각 노드에서 하는 일(\(O(1)\) push_down 포함)도 상수 시간이다. 결국 갱신·질의가
모두 \(O(\log N)\)으로 떨어진다.
주의할 점은 곱하기와 더하기가 섞이면 lazy의 결합 순서가 중요해진다는
것이다. "먼저 곱하고 더한다" 같은 우선순위를 정해 두 종류의 lazy를 함께
관리해야 하며, 이때 전파 공식은 \(a \cdot x + b\) 꼴의 affine 변환 합성으로
일반화된다.