Segment Tree
Top-Down
코드
설명
계산 편의를 위해 0
번 노드는 사용하지 않음
초기값은 nodes
에 직접넣고 Init()
으로 처리하면 빠름.
INDEX_MAX
는 첫번째 리프노드의 위치를 의미함.
Bottom-Up
코드
설명
이진트리의 특성을 이용해서 부모 및 형제 노드로 움직여서 구현함.
Update()
- 형제 노드는 자신이 홀이면
-1
한 값, 짝이면 +1
한 값임
Query()
l
인 경우
- 계속 2를 나누다가 홀수가 되면
l
이상의 값만 포함하는 최상위 노드
- 짝수일 때는 부모노드가 결과에 포함이 됨
- 홀수인 경우엔 아님. 형제 노드가 범위 밖이라 부모노드를 더하면 안됨
- 이 노드는
l
이상의 모든 값을 포함하는 게 아님
- 바로
l++
해줘서
- 그 이상의 값을 포함하는 최상위 노드를 똑같은 방법으로 구하면 됨
r
의 경우도 마찬가지
예를들어 8개의 수가 있을 때 [2, 7] 의 숫자합은 (1부터 셈)
- 노드는 총 15 개가 있을 것이고
- 위에서부터 높이 순으로 값을 넣었을 때
l
은 9, 5 = (9+1)/2, 3 = (5+1)/2 …
r
은 14, 6 = (14-1)/2, 2 = (6 - 1)/2 …
- 그래서 9, 5, 6, 14 번 노드를 더하게 됨.
Lazy Segment Tree
코드
설명
값 하나가 아닌 연속된 값들을 바꾸는 경우 사용되는 최적화 기법.
Node
에 Lazy 용 변수를 하나 더 추가함.
- 주어진 범위를 표현하는 최소한의 노드 만 수정함
- 즉 가능한 높은 노드 만 사용
- 그래서 Bottom-Up 보다는 Top-Down 으로 많이 구현함
- 수정할 노드의 포괄하는 범위가 수정해야하는 범위 내에 있는 경우
- 현재 노드에서
수정할 값 * 현재 노드가 포괄하는 범위
만큼 수정하면 됨.
- 수정한 노드의 바로 아래 자식 노드에게는
Lazy
값만 수정하고 재귀는 생략함.
- 후에
Lazy
값을 가진 노드에 접근할 때 마다
Lazy
를 바로 아래 자식에게 넘겨주고 자기자신만 업데이트해줌.
Persistent SegmentTree
코드
설명
간단 유튜브 설명
TopDown 방식과 거의 비슷하나, 업데이트 될 때마다 새로운 노드로 덮어쓰는게 다름.
- 그래서 기존 힙과는 다르게 포인터로 노드를 직접 연결해줘야함.
Fenwick Tree
코드
설명
Bindary Indexed Tree (BIT) 라고도 불림.
구현 관련 BOJ Blog
- 2의 보수 를 응용해 가장 낮은 1의 위치를 찾아냄
Nodes[idx]
는 Input[idx - (idx & -idx) + 1] ~ Input[idx]
의 합을 나타냄
- 예를들어 1~7 번째 까지의 합을 구한다 하면
Nodes[1000]
은 1 ~ 4 번째의 합
Nodes[1010]
은 5 ~ 6 번째의 합
Nodes[1011]
은 7 ~ 7 번째의 합
- 1 ~ 7 =>
Nodes[1000] + nodes[1010] + odes[1011]
이 됨.
누적합과 매우 비슷함
- 누적합이 값을 수정하는데 O(n), 값을 구하는데 O(1) 이지만
- BIT 는 값을 수정하는데 O(logN), 값을 구하는데 O(logN) 이 걸림.
- 이미 주어진 리스트에 초기값을 구성하는데 O(nLogN) 이 걸림.
Segtree 와도 비슷함
- 탐색 및 수정은 똑같이 O(logN) 이지만 조금 더 빠름.
- 그리고 공간 복잡도가 O(n) 임
- 하지만 Lazy, Persistent 등의 응용은 힘듬.
2D Fenwick Tree
코드
설명
검색, 수정에 O(logN * logN) 이 걸림
- n 개의 펜윅트리를 구현하는 하기좋은 실수가 있는데 이러면 O(nlogN) 이 됨.
같은 방법으로 다차원 펜윅 트리를 만들 수 있음.
- 세그먼트 트리도 차원을 펼칠 수 있지만 펜윅이 훨신 코드가 쉬움
예를들어 Query(2, 3)
이러면 (x = 1 ~ 2, y = 1 ~ 3)
범위의 합이 됨.
- 특정 구간을 원하면 직사각형 범위를 쪼개서
A - B - C + D
이런 식이 필요함
댓글남기기