동기
DP 값 함수 \(f_i(x)\) 가 항상 볼록 조각별 선형(piecewise linear convex) 함수가 되는 문제가 많다. 예: 각 위치에서 값을 \(\pm 1\) 조정하는 비용의 누적 최소화, 비감소 수열로 만들기 최소비용 등. 함수 전체를 들고 다니면 비싸지만, 볼록 PL 함수는 기울기가 바뀌는 점들(꺾임점) 만으로 완전히 결정된다. 슬로프 트릭은 이 꺾임점들을 우선순위 큐로 관리한다.
표현
볼록 PL 함수 \(f\) 는
- 최솟값 \(f_{\min}\),
- 기울기가 1씩 감소하는 왼쪽 꺾임점들의 multiset \(L\) (최댓값이 top인 max-heap),
- 기울기가 1씩 증가하는 오른쪽 꺾임점들의 multiset \(R\) (최솟값이 top인 min-heap)
로 표현한다. \(L\) 의 최댓값과 \(R\) 의 최솟값 사이가 최소 구간(기울기 0)이다. 각 꺾임점은 "거기서 기울기가 1 변한다"는 단위 정보로 정규화한다(기울기가 2 변하면 같은 점을 두 번 넣는다).
기본 연산
| 연산 | \(L,R\) 변화 |
|---|---|
| \(f(x) \mathrel{+}= |x-a|\) | \(a\) 를 \(L,R\) 양쪽에 넣고, 교차하면 교환하며 \(f_{\min}\) 보정 |
| \(f(x)\mathrel{+}= \max(0, x-a)\) | \(a\) 를 \(R\) 에 (오른쪽만 기울기 증가) |
| \(f(x)\mathrel{+}=\max(0, a-x)\) | \(a\) 를 \(L\) 에 |
| 전체 \(x\) 평행이동 (shift) | heap의 lazy 오프셋 |
| prefix-min (\(g(x)=\min_{y\le x}f(y)\)) | \(R\) 비움 (오른쪽 상승 제거) |
\(|x-a|\) 추가의 정확성
\(|x-a| = \max(0,a-x)+\max(0,x-a)\) 이므로 왼·오른쪽에 각각 \(a\) 를 넣는다. 그런데 두 힙의 최댓값/최솟값 순서가 어긋나면(즉 \(a\) 가 현재 최소 구간을 벗어나면) 두 top을 교환하고 그 차이만큼 \(f_{\min}\) 이 증가한다. 이 보정이 슬로프 트릭의 핵심 한 줄이다.
복잡도
각 단계가 힙 연산 \(O(\log n)\), 단계가 \(n\) 개면 전체 \(O(n\log n)\). 함수 전체를 \(O(n)\) 으로 들고 갱신하던 것을 꺾임점만으로 압축한 결과다.