├── blog.css ├── header.css ├── index.html ├── main.css ├── blogSample.html └── blog_LCA_1.html /blog.css: -------------------------------------------------------------------------------- 1 | .blogHeader { 2 | display: block; 3 | width: 75%; 4 | margin: 0 auto 10px; 5 | background: #D7C5C1; 6 | font-size: 64px; 7 | font-weight: bold; 8 | box-sizing: border-box; 9 | padding-left: 10px; 10 | } 11 | 12 | .blogMain { 13 | display: block; 14 | width: 75%; 15 | margin: 0 auto; 16 | background-color: #DDD; 17 | } 18 | -------------------------------------------------------------------------------- /header.css: -------------------------------------------------------------------------------- 1 | body {margin:0;} 2 | ul { 3 | list-style: none; 4 | margin: 0 auto; 5 | } 6 | a { 7 | text-decoration: none; 8 | transition: 1s linear; 9 | } 10 | header { 11 | display: block; 12 | width: 75%; 13 | margin: 0 auto 10px; 14 | } 15 | header ul { 16 | padding: 1em 0; 17 | background: #D7C5C1; 18 | } 19 | header a { 20 | padding: 1em; 21 | background: rgba(177, 152, 145, .3); 22 | border-right: 1px solid #b19891; 23 | color: #695753; 24 | } 25 | header a:hover {background: #b19891;} 26 | header li { 27 | display: inline; 28 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | aleks5d blog 5 | 6 | 7 | 8 | 9 |
10 | 14 |
15 |
16 | 17 |
LCA - 1
18 |
Тут я рассказываю о LCA в целом и о бинарных подъемах
19 |
20 |
21 | 24 | 25 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | body { 2 | } 3 | 4 | main { 5 | display: block; 6 | width: 75%; 7 | margin: 0 auto; 8 | background-color: #DDD; 9 | } 10 | 11 | code { 12 | background-color: white; 13 | display: block; 14 | } 15 | 16 | .blogPre { 17 | display: block; 18 | min-height: 200px; 19 | border: 2px solid black; 20 | width: 90%; 21 | background-color: #CCC; 22 | margin: 0 auto 10px; 23 | box-sizing: border-box; 24 | text-decoration: none; 25 | color: black; 26 | } 27 | 28 | .blogPre .name { 29 | display: block; 30 | width: 100%; 31 | box-sizing: border-box; 32 | padding-left: 10px; 33 | font-size: 52px; 34 | font-weight: bold; 35 | background-color: #D7C5C1; 36 | border-bottom: 2px solid black; 37 | } 38 | 39 | .blogPre .description { 40 | display: block; 41 | width: 70%; 42 | margin: 0 auto; 43 | font-size: 24px; 44 | } -------------------------------------------------------------------------------- /blogSample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aleks5d blog 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 19 |
20 |
21 |
22 | Название блога 23 |
24 |
25 | Привет мир 26 |
27 |
28 | 31 | 32 | -------------------------------------------------------------------------------- /blog_LCA_1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aleks5d blog 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 19 |
20 |
21 |
22 | LCA-1 23 |
24 |
25 |

LCA - lowest (least) common ancestor
26 | Наименьший общий предок двух вершин

27 |

28 | Для начала давайте разберемся, что такое общий предок двух вершин \(u\) и \(v\).
29 | Это любая такая вершина \(t\), которая лежит на пути от корня до \(u\) и на пути от корня до \(v\). 30 |

31 |

32 | Тогда наименьшим общим предком \(u\) и \(v\) назовем такую вершину \(t\), которая является их общим предком, и расстояние от корня до нее максимально. 33 |

34 |

35 | Зачем нужно уметь его искать?
36 | В последнее время я все реже встречаю задачи, требующие именно явного нахождения наименьшего общего предка, но порой попадаются задачи про запросы на пути, а с ними нам может помочь LCA. 37 |

38 |

Наивная реализация

39 |

40 | Для начала рассмотрим самую простую реализацию поиска LCA: 41 |

42 |

 43 | 	vector<vector<int>> gr; \\ наш граф
 44 | 	vector<int> pre; \\ массив предков
 45 | 	vector<int> d; \\ массив глубин
 46 | 
 47 | 	void dfs(int v, int p = -1) {
 48 | 		if (p == -1)
 49 | 			d[v] = 0;
 50 | 		else
 51 | 			d[v] = d[p] + 1;
 52 | 		pre[v] = p;
 53 | 		for (int to : gr[v]) 
 54 | 			if (to != p)
 55 | 				dfs(to, v);
 56 | 	}
 57 | 
 58 | 	int getLCA(int a, int b) {
 59 | 		while (a != b) {
 60 | 			if (d[a] > d[b]) 
 61 | 				a = pre[a];
 62 | 			else
 63 | 				b = pre[b];
 64 | 		}
 65 | 		return a;
 66 | 	} 
 67 | 
 68 | 	int main() {
 69 | 		... // считываем граф
 70 | 		dfs(root) // запускаем дфс от корня
 71 | 
 72 | 		for () {
 73 | 			... // считываем запрос
 74 | 			getLCA(a, b); // отвечаем на него
 75 | 		}
 76 | 	}
 77 | 
78 |

79 | Почему это верно? 80 |

81 |

82 | Давайте посмотрим, что делает наш алгоритм. Пусть сейчас выбраны вершины \(a\) и \(b\).
83 | Их LCA имеет глубину не больше, чем минимум из глубин \(a\) и \(b\), так как лежит на пути от корня до этих вершин.
84 | Значит, если глубины вершин неравны, то можно смело заменять более глубокую вершину ее предком.
85 | Теперь допустим, что глубины вершин одинаковы.
86 | Тогда если они равны, то очевидно что это и есть LCA.
87 | Иначе, LCA имеет губину меньшую, чем та, на которой сейчас расположены вершины, и опять можно заменить их своими предками.
88 |

89 |

90 | Отлично, теперь давайте оценим время работы.
91 | dfs() отработает за O(n)
92 | getLCA() в худшем случае будет работать за O(n) (например, если граф - бамбук, и мы спрашиваем ответ от его концов)
93 | Таким образом, алгоритм работает за O(q * n) - достаточно долго.
94 |

95 |

96 | Давайте попробуем ускорить наш алгоритм. 97 |

98 |

Метод двоичных подъёмов

99 |

100 | Если очень внимательно посмотреть на предыдущую реализацию, то можно заметить два этапа в ней: 101 |

102 |

103 | Первый - глубина вершин выравнивается. 104 |

105 |

106 | Второй - вершины параллельно поднимаются наверх, пока не станут равными. 107 |

108 |

109 | Тогда давайте ходить не по 1, а большими блоками. Например, степенями двойки. 110 |

111 |

112 | Для начала разберемся, как их предпосчитать. 113 |

114 |


115 | 	vector<vector<int>> gr;
116 | 	vector<int> d;
117 | 	vector<vector<int>> bl; // бинарные подъемы
118 | 	// этот массив следует инициализировать так: bl[log(n)+1][n]
119 | 	// log(n)+1 ассив по n элементов
120 | 
121 | 	void dfs(int v, int p = -1) {
122 | 		if (p == -1) { 
123 | 			p = v;
124 | 			d[v] = 0;
125 | 		} 
126 | 		else
127 | 			d[v] = d[p] + 1;
128 | 		bl[0][v] = p;
129 | 		for (int i = 1; i < bl.size(); ++i)
130 | 			bl[i][v] = bl[i-1][bl[i-1][v]];
131 | 		for (int to : gr[v])
132 | 			dfs(to, v);
133 | 	}
134 | 

135 |

136 | Почему это работает? В цикле построения bl мы говорим, что подняться на \(2^i\) то же самое, что подняться на \(2^{i-1}\) два раза. 137 |

138 |

139 | Как можно заметить, в такой реализации нельзя подняться выше корня, а значит, не нужны дополнительные if. 140 |

141 |

142 | Как же тут отвечать на запрос? Давайте разбираться.
143 |

144 |


145 | 	int getLCA(int a, int b) {
146 | 		if (d[a] > d[b])
147 | 			swap(a, b); 
148 | 		// Теперь b глубже a
149 | 		for (int i = bl.size() - 1; i >= 0; --i) 
150 | 			if (d[bl[i][b]] >= d[a])
151 | 				b = bl[i][b];
152 | 		// Теперь их глубины равны
153 | 		if (a == b)
154 | 			return a; // Если они равны, то это и есть LCA
155 | 
156 | 		for (int i = bl.size() - 1; i >= 0; --i)
157 | 			if (bl[i][b] != bl[i][a]) {
158 | 				a = bl[i][a];
159 | 				b = bl[i][b];
160 | 			}
161 | 		//Теперь верно, что a != b и их глубина минимальна
162 | 		return bl[0][a];
163 | 	}
164 | 

165 |

166 | Теперь есть два варианта:
167 | Первый - вы верите мне, что это работает. Тогда делайте *ТЫК* и читайте дальше
168 | Второй - вы хотите понять, что вообще произошло. Тогда продолжайте читать дальше. 169 |

170 |

171 | Для начала докажем, что работает первый этап и глубина b становится равной глубине a. 172 |

173 |

174 | Рассмотрим число \(x = (d[b] - d[a])\),
тогда когда мы пойдем от меньшего бита к большему, мы будем совершать переход только тогда, когда этот бит есть в числе.
175 | Почему? Потому что если степень двойки больше, чем максимальный бит числа, то эта степень точно больше самого числа.
176 | Значит, мы будем всегда совершать переход по самому большому биту числа.
177 | После этого расстояние уменьшится на эту степень двойки, и мы перейдем к меньшему расстоянию, с которым по аналогии возьмем переход в соответствии с максимальным битом. 178 | Кстати, на этом можно основать некоторую оптимизацию. Вместо цикла
179 |


180 | 	for (int i = bl.size() - 1; i >= 0; --i)
181 | 		if (d[bl[i][b]] >= d[a])
182 | 			b = bl[i][b];
183 | 
184 | писать 185 |

186 | 	int x = d[b] - d[a];
187 | 	for (int i = bl.size() - 1; i >= 0; --i)
188 | 		if (x & (1 << i))
189 | 			b = bl[i][b];
190 | 
191 | Так как тут меньше операций и они более простые, то и работать будет быстрее (чуть-чуть). 192 |

193 |

194 | А что происходит на втором этапе? 195 |

196 |

197 | Вообще, мы хотим максимально подняться так, чтобы a было все еще не было равно b. 198 |

199 |

200 | Значит, есть какая-то глубина x, на которую нам хотелось бы подняться, но мы ее не знаем. У нас есть условие \(a \neq b\), которое мы и будем использовать. 201 |

202 |

203 | Опять таки, мы не можем перейти по биту больше, чем максимальный, а по максимальному можем всегда. Так мы поднимемся максимально и сохраним \(a\neq b\), а значит, их предок уже таков, что \(pre[a] = pre[b]\), следовательно, это и есть LCA. 204 |

205 |

Что еще умеют бинподъемы?

206 |

207 | Например, можно посчитать какую-то функцию на пути, параллельно с поиском LCA. 208 |

209 |

210 | Для этого давайте хранить bl как массив пар вида \( [to, f] \) - куда нужно перейти, и какое значение функции на этом переходе. 211 |

212 |

213 | Покажу на примере минимума. Он хорош тем, что нельзя брать его на пути до корня, а потом вычитать ответ на пути от LCA до корня.
214 | Например, сумму можно считать так: если \(sum[v]\) - сумма на пути до вершины v, то сумма на пути от a до b считается как \(sum[a] + sum[b] - 2 * sum[LCA]\). Это если значения записаны на ребрах.
С минимумом так не выйдет. 215 |

216 |


217 | 	vector<vector<pair<int, int>>> gr;
218 | 	vector<int> d;
219 | 	vector<vector<pair<int, int>>> bl;
220 | 
221 | 	void dfs(int v, int p = -1, int pEdge = inf) {
222 | 		if (p == -1) { 
223 | 			p = v;
224 | 			d[v] = 0;
225 | 		}
226 | 		else
227 | 			d[v] = d[p] + 1;
228 | 		bl[0][v] = {p, pEdge};
229 | 		for (int i = 1; i < bl.size(); ++i)
230 | 			bl[i][v] = {bl[i-1][bl[i-1][v].first].first, min(bl[i-1][v].second, bl[i-1][bl[i-1][v].first].second);
231 | 		for (pair<int, int> to : gr[v])
232 | 			dfs(to.first, v, to.second);
233 | 	}
234 | 
235 | 	int getMIN(int a, int b) {
236 | 		int ans = inf;
237 | 
238 | 		if (d[a] > d[b])
239 | 			swap(a, b); 
240 | 		for (int i = bl.size() - 1; i >= 0; --i) 
241 | 			if (d[bl[i][b].first] >= d[a]) {
242 | 				ans = min(ans, bl[i][b].second);
243 | 				b = bl[i][b].first;
244 | 			}
245 | 		if (a == b)
246 | 			return ans;
247 | 
248 | 		for (int i = bl.size() - 1; i >= 0; --i)
249 | 			if (bl[i][b].first != bl[i][a].first) {
250 | 				ans = min(ans, bl[i][b].second);
251 | 				ans = min(ans, bl[i][a].second);
252 | 				a = bl[i][a].first;
253 | 				b = bl[i][b].first;
254 | 			}
255 | 		ans = min({ans, bl[i][b].second, bl[i][a].second});
256 | 		return ans;
257 | 	}
258 | 

259 |

260 | Ну и самое важное: время и память!
261 | Как можно заметить, алгоритм требует \(O(n \cdot log(n))\) памяти и \(O(n \cdot log(n))\) времени на предподсчет
262 | Но вот ответ на запрос занимает всего \(O(log(n))\)
263 | Получаем \(O(n \cdot log(n) + q \cdot log(n))\) 264 |

265 |

А как еще можно?

266 |

267 | Можно свести LCA к RMQ и решать его любыми удобными вам методами - ДО, Sparse-table. 268 |

269 |

270 | Как свести? Давайте выпишем Эйлеров обход графа. Тогда ответом будет вершина на полуинтервале \([min(first[a], first[b]), max(last[a], last[b]))\), глубина которой минимальна. 271 |

272 |

273 | Здесть first и last - массивы первых и последних вхождений вершины в Эйлеров обход. 274 |

275 |


276 | 	vector<vector<int>> gr;
277 | 	vector<int> d;
278 | 	vector<pair<int, int>> euler;
279 | 	vector<int> first;
280 | 	vector<int> last;
281 | 
282 | 	void dfs(int v, int p = -1) {
283 | 		if (p == -1)
284 | 			d[v] = 0;
285 | 		else
286 | 			d[v] = d[p] + 1;
287 | 		first[v] = euler.size();
288 | 		euler.push_back({v, d[v]});
289 | 		for (int to : gr[v]) {
290 | 			dfs(to, v);
291 | 			euler.push_back({v, d[v]});
292 | 		}
293 | 		last[v] = euler.size();
294 | 	}
295 | 

296 |

297 | Теперь можно взять первый элемент и минимум по вторым элементам пар на полуинтервале, это и будет LCA. 298 |

299 |

300 | Почему так? Эйлеров обход записывает вершину в самом начале и после обхода каждого поддерева. То есть, если минимум на отрезке - какая-то вершина v, то две данные находятся в разных ее поддеревьях. 301 |

302 |

303 | Какие плюсы и минусы?
304 | К плюсам можно отнести то, что используя столько же памяти, сколько бинарные подъемы, можно построить sparse-table и отвечать на запросы за O(1).
305 | А также, что можно использовать O(n) памяти, например - ДО.
306 | Минусы - нельзя вычислять что-то на пути (только если это не Мо... Но это лучше даже в голову не брать.)
307 |

308 |

А ещё?

309 |

Есть алгоритм, требующий \(O(n)\) как памяти, так и времени на предподсчет. Отвечает на запрос он за \(O(1)\).
310 | Но рассказывать его я не буду (пока уж точно), так как он мало применим на практике и будет бесполезен на олимпиадах.

311 | 312 |

ВАЖНО!

313 |

314 | Все алгоритмы, описанные в данном тексте, умеют отвечать на запросы онлайн. Запрос пришел - мы ответили.
315 | Бинарные подъемы умеют даже добавлять вершину в дерево. Для этого нужно просто представить, что мы пришли в нее в ходе дфс, и пройтись циклом по степеням двойки.
316 | Если все запросы известны заранее, лучше использовать алгоритм Тарьяна. Про него я напишу отдельно. 317 |

318 |
319 |
320 | 323 | 324 | --------------------------------------------------------------------------------