├── 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 |
28 | Для начала давайте разберемся, что такое общий предок двух вершин \(u\) и \(v\).
29 | Это любая такая вершина \(t\), которая лежит на пути от корня до \(u\) и на пути от корня до \(v\).
30 |
32 | Тогда наименьшим общим предком \(u\) и \(v\) назовем такую вершину \(t\), которая является их общим предком, и расстояние от корня до нее максимально. 33 |
34 |
35 | Зачем нужно уметь его искать?
36 | В последнее время я все реже встречаю задачи, требующие именно явного нахождения наименьшего общего предка, но порой попадаются задачи про запросы на пути, а с ними нам может помочь LCA.
37 |
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 |
90 | Отлично, теперь давайте оценим время работы.
91 | dfs() отработает за O(n)
92 | getLCA() в худшем случае будет работать за O(n) (например, если граф - бамбук, и мы спрашиваем ответ от его концов)
93 | Таким образом, алгоритм работает за O(q * n) - достаточно долго.
94 |
96 | Давайте попробуем ускорить наш алгоритм. 97 |
98 |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 |
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 |
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 |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 |
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 |
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 |
Есть алгоритм, требующий \(O(n)\) как памяти, так и времени на предподсчет. Отвечает на запрос он за \(O(1)\).
310 | Но рассказывать его я не буду (пока уж точно), так как он мало применим на практике и будет бесполезен на олимпиадах.
314 | Все алгоритмы, описанные в данном тексте, умеют отвечать на запросы онлайн. Запрос пришел - мы ответили.
315 | Бинарные подъемы умеют даже добавлять вершину в дерево. Для этого нужно просто представить, что мы пришли в нее в ходе дфс, и пройтись циклом по степеням двойки.
316 | Если все запросы известны заранее, лучше использовать алгоритм Тарьяна. Про него я напишу отдельно.
317 |