Splay Tree¶
Problem¶
다음 연산들을 online으로 지원할 수 있는 dynamic array를 구현하여라.
- Find_kth : \(k\)번째 원소를 구한다.
- Insert : \(k\)번째 원소 바로 뒤에 원소 \(x\)를 삽입한다.
- Erase : \(k\)번째 원소를 삭제한다.
- Update : 구간 \([l, r]\)에 업데이트를 적용한다. (lazy propagation이 가능한 연산)
- Query : 구간 \([l, r]\)에 쿼리를 날린다.
Algorithm¶
배열의 원소들을 이진트리의 형태로 관리한다. 각 노드에는 (서브트리의 크기, 부모 노드, 왼쪽 자식, 오른쪽 자식, 원소의 값, 서브트리에 대한 쿼리의 답, lazy 값)을 저장한다.
노드 \(x\)의 lazy 값의 정의는 \(x\)를 제외한 \(x\)의 서브트리에 적용해야 할 누적된 업데이트들을 의미한다. 즉, 노드 \(x\)의 값들에는 이미 업데이트가 적용이 되어 있어야 한다.
균형된 이진트리를 유지하기 위하여 Splay 연산을 이용한다.
-
Rotate
노드 \(x\)를 \(x\)의 부모 노드 \(p\)의 위치로 올린다.
(\(x\)의 \(p\) 방향 자식을 노드 \(q\)라 했을 때, \(x\)의 부모, \(p\)의 부모, \(q\)의 부모, \(x\)의 \(p\) 방향 자식, \(p\)의 \(x\) 방향 자식, \(p\)의 부모 노드의 자식 이 변한다.) -
Splay
노드 \(x\)를 루트로 만든다. 다음 과정을 반복한다.
- \(x\)가 루트이면, 종료한다.
- \(x\)의 부모가 루트이면, Rotate(\(x\)) 한다. (Zig step)
- \(x\)의 부모 \(p\)의 방향과, \(p\)의 부모 \(g\)의 방향이 같다면 Rotate(\(p\)), Rotate(\(x\)) 한다. (Zig-Zig step)
- \(x\)의 부모 \(p\)의 방향과, \(p\)의 부모 \(g\)의 방향이 다르다면 Rotate(\(x\)), Rotate(\(x\)) 한다. (Zig-Zag step)
Splay 연산은 ammortized \(O(\log N)\)의 시간복잡도를 갖고 있기 때문에, 트리 내부의 노드를 접근할 때에는 시간을 소모한 후 splay 연산을 실행하여야 한다.
Complexity
Ammortized \(O(\log N)\)
Splay 연산을 이용하여 나머지 기능들을 다음과 같이 구현한다.
-
Find_Kth
루트에서 시작하여 \(k\)번째 노드가 어느 쪽 서브트리에 속하는지 판별하고 재귀적으로 내려간다. \(k\)번째 루트를 찾으면, 이 노드를 Splay하여 루트로 만든다.
-
Insert
루트에서 시작하여 새로 삽입할 \(k\)번째 다음 위치의 노드가 어느 쪽 서브트리에 속하는지 판별하고 재귀적으로 내려간다. 더 이상 내려갈 수 없을 때, 새로운 노드를 자식으로 삽입한 후, 삽입한 노드를 Splay한다.
-
Erase
Find_Kth 연산을 이용하여 삭제할 \(k\)번째 원소를 루트로 만든다. 루트를 삭제하기 위하여, Find_Kth 연산을 이용하여 루트의 왼쪽 서브트리 \(l\)의 가장 오른쪽 노드 \(p\)를 루트로 만들고, \(p\)의 오른쪽 자식으로 루트의 오른쪽 서브트리 \(r\)을 삽입한다.
-
Interval (Update / Query)
구간 \([l, r]\)의 노드들을 한 서브트리로 모은다. 우선 Find_Kth 연산을 이용하여 \(r+1\)번째 노드를 루트로 만든다. 루트의 왼쪽 서브트리에서 Find_Kth 연산을 이용하여 \(l-1\)번째 노드를 루트의 왼쪽 자식으로 만든다. 루트의 왼쪽 자식의 오른쪽 자식의 서브트리가 구간 \([l, r]\)을 모아놓은 서브트리이다. 이 노드에 대하여 Update / Query를 실행한다.
Complexity
Ammortized \(O(\log N)\)
Code¶
splay_tree.cpp | |
---|---|
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
|
Details¶
Node
val
: 현재 노드에 적혀 있는 원소의 값sum
: 현재 노드의 서브트리에 대한 쿼리의 값 (저장해야 할 값에 따라 바꾸어 사용)lazy
: 현재 노드를 제외하고, 현재 노드의 서브트리에 적용해야 할 누적된 lazy 값 (현재 노드에는 이미 업데이트가 적용되어 있음)Node(ll x) {}
: 원소 \(x\)를 의미하는 새로운 노드를 생성함
SplayTree
NS[0]
은NIL
노드를 의미함- 전체 트리들이 꼭 하나로 연결되어 있을 필요는 없으며, 현재 작업 중인 트리의 루트가
root
에 담겨 있어야 함 - 문제 상황의 연산에 따라
recalc
,apply
,prop
을 구현하여 사용 void recalc(int node) {}
: 왼쪽 자식, 오른쪽 자식의 값을 이용하여 현재 노드node
의 값을 다시 계산함node
의lazy
값이 비어 있어야 함node
의sz
,sum
을 바꿈
void apply(int node, ll upd) {}
: 현재 노드node
의 서브트리에upd
를 업데이트함- 현재 노드에 직접 (
sum
,val
) 업데이트를 하고,lazy
에도 따로 업데이트를 해야 함 node
의lazy
,sum
,val
을 바꿈
- 현재 노드에 직접 (
void prop(int node) {}
: 현재 노드node
의lazy
에 쌓여 있는 업데이트를 자식 노드로 전파함- 자식 노드로 내려가기 전에 호출되어야 함
lazy
값을 초기화함
void prop_anc(int x)
: 노드 \(x\)의 조상 노드들에 대해 모두 순서대로prop
을 호출함void rotate(int x) {}
: 노드 \(x\)를 \(x\)의 부모 노드 \(p\)의 위치로 올림- \(x\)는 루트가 아니여야 함
void splay(int x) {}
: 노드 \(x\)를root
로 만듬- 트리 내부의 노드를 접근할 때에는 시간을 소모한 후 splay 연산을 실행해야 함
int find_kth(int node, int k) {}
: 노드node
의 서브트리의 \(k\)번째 노드 번호를 리턴함int find_kth(int k) {}
: 전체 트리의 \(k\)번째 노드 번호를 리턴함void insert(int node, int k, int x) {}
:노드node
의 \(k\)번째 노드 바로 뒤에 \(x\)번째 노드를 삽입함void insert(int k, int x) {}
: 전체 트리의 \(k\)번째 노드 바로 뒤에 \(x\)번째 노드를 삽입함void erase() {}
: 루트 노드를 삭제함void update(int l, int r, ll val) {}
: 전체 트리의 구간 \([l, r]\)에 \(val\)을 업데이트함Node query(int l, int r) {}
: 전체 트리의 구간 \([l, r]\)에 해당하는 노드를 리턴함