Метод двоичного подъёма — различия между версиями
Shersh (обсуждение | вклад) (→См. также: поправлены ссылки) |
м (rollbackEdits.php mass rollback) |
||
(не показано 38 промежуточных версий 11 участников) | |||
Строка 1: | Строка 1: | ||
− | '''Метод двоичного | + | '''Метод двоичного подъёма''' {{---}} один из самых простых методов для решения задачи [[Сведение задачи LCA к задаче RMQ|LCA]] в online. Он не использует метод решения задачи '''RMQ''' и основан на методе [[Динамическое программирование | динамического программирования]]. |
==Описание алгоритма== | ==Описание алгоритма== | ||
Как и большинство '''on-line''' алгоритмов для решения задачи [[Сведение задачи LCA к задаче RMQ|LCA]], этот метод делает сначала препроцессинг, чтобы потом отвечать на запросы. | Как и большинство '''on-line''' алгоритмов для решения задачи [[Сведение задачи LCA к задаче RMQ|LCA]], этот метод делает сначала препроцессинг, чтобы потом отвечать на запросы. | ||
===Препроцессинг=== | ===Препроцессинг=== | ||
− | Препроцессинг заключается в том, чтобы посчитать функцию: <tex> dp[v][i] </tex> {{---}} номер вершины, в которую мы | + | Препроцессинг заключается в том, чтобы посчитать функцию: <tex> dp[v][i] </tex> {{---}} номер вершины, в которую мы придём если пройдём из вершины <tex> v </tex> вверх по подвешенному дереву <tex> 2 ^ i </tex> шагов, причём если мы пришли в корень, то мы там и останемся. |
− | Для этого сначала обойдем дерево в глубину и для каждой вершины запишем номер | + | Для этого сначала обойдем дерево в глубину, и для каждой вершины запишем номер её родителя <tex> p[v] </tex> и глубину вершины в подвешенном дереве <tex> d[v] </tex>. Если <tex> v </tex> {{---}} корень, то <tex> p[v] = v </tex>. Тогда для функции <tex> dp </tex> есть рекуррентная формула: |
<tex>dp[v][i]= \begin{cases} | <tex>dp[v][i]= \begin{cases} | ||
p[v] & i = 0,\\ | p[v] & i = 0,\\ | ||
− | dp[dp[v][i - 1]][i - 1] & i > 0. | + | dp[dp[v][i - 1]][i - 1] & i \: > \: 0. |
\end{cases}</tex> | \end{cases}</tex> | ||
− | Для того чтобы отвечать на запросы нам нужны будут только те значения <tex> dp[v][i] </tex>, где <tex> i \ | + | Для того чтобы отвечать на запросы нам нужны будут только те значения <tex> dp[v][i] </tex>, где <tex> i \leqslant \log_2{n} </tex>, ведь при больших <tex> i </tex> значение <tex> dp[v][i] </tex> будет номером корня. |
Всего состояний динамики <tex> O(n \log{n})</tex>, где <tex> n </tex> {{---}} это количество вершин в дереве. Каждое состояние считается за <tex> O(1) </tex>. Поэтому суммарная сложность времени и памяти препроцессинга {{---}} <tex> O(n \log{n}) </tex>. | Всего состояний динамики <tex> O(n \log{n})</tex>, где <tex> n </tex> {{---}} это количество вершин в дереве. Каждое состояние считается за <tex> O(1) </tex>. Поэтому суммарная сложность времени и памяти препроцессинга {{---}} <tex> O(n \log{n}) </tex>. | ||
Строка 17: | Строка 17: | ||
===Ответы на запросы=== | ===Ответы на запросы=== | ||
Ответы на запросы будут происходить за время <tex> O(\log{n})</tex>. | Ответы на запросы будут происходить за время <tex> O(\log{n})</tex>. | ||
− | Для ответа на запрос заметим сначала, что если <tex> c = LCA(v, u) </tex>, для некоторых <tex> v </tex> и <tex> u </tex>, то <tex> d[c] \ | + | Для ответа на запрос заметим сначала, что если <tex> c = LCA(v, u) </tex>, для некоторых <tex> v </tex> и <tex> u </tex>, то <tex> d[c] \leqslant \min(d[v], d[u])</tex>. Поэтому если <tex> d[v] < d[u] </tex>, то пройдём от вершины <tex> u </tex> на <tex> (d[u] - d[v]) </tex> шагов вверх, это и будет новое значение <tex> u </tex> и это можно сделать за <tex> O(\log{n}) </tex>. Можно записать число <tex> (d[u] - d[v]) </tex> в двоичной системе, это представление этого число в виде суммы степеней двоек, <tex> 2 ^ {i_1} + 2 ^ {i_2} + \ldots + 2 ^ {i_l} </tex> и для всех <tex> i_j</tex> пройти вверх последовательно из вершины <tex> u </tex> в <tex> dp[u][i_j] </tex>. |
Дальше считаем, что <tex> d[v] = d[u] </tex>. | Дальше считаем, что <tex> d[v] = d[u] </tex>. | ||
Строка 23: | Строка 23: | ||
Если <tex> v = u </tex>, то ответ на запрос <tex> v </tex>. | Если <tex> v = u </tex>, то ответ на запрос <tex> v </tex>. | ||
− | А если <tex> v \neq u </tex>, то | + | А если <tex> v \neq u </tex>, то найдём такие вершины <tex> x </tex> и <tex> y </tex>, такие что <tex> x \neq y </tex>, <tex> x </tex> {{---}} предок <tex> v </tex>, <tex> y </tex> {{---}} предок <tex> u </tex> и <tex> p[x] = p[y] </tex>. Тогда ответом на запрос будет <tex> p[x] </tex>. |
Научимся находить эти вершины <tex> x </tex> и <tex> y </tex>. Для этого сначала инициализируем <tex> x = v </tex> и <tex> y = u </tex>. Дальше на каждом шаге находим такое максимальное <tex> k </tex>, что <tex> dp[x][k] \neq dp[y][k] </tex>. И проходим из вершин <tex> x </tex> и <tex> y </tex> на <tex> 2 ^ k </tex> шагов вверх. Если такого <tex> k </tex> найти нельзя, то значения <tex> x </tex> и <tex> y </tex>, это те самые вершины, которые нам требуется найти, ведь <tex> p[x] = dp[x][0] = dp[y][0] = p[y] </tex>. | Научимся находить эти вершины <tex> x </tex> и <tex> y </tex>. Для этого сначала инициализируем <tex> x = v </tex> и <tex> y = u </tex>. Дальше на каждом шаге находим такое максимальное <tex> k </tex>, что <tex> dp[x][k] \neq dp[y][k] </tex>. И проходим из вершин <tex> x </tex> и <tex> y </tex> на <tex> 2 ^ k </tex> шагов вверх. Если такого <tex> k </tex> найти нельзя, то значения <tex> x </tex> и <tex> y </tex>, это те самые вершины, которые нам требуется найти, ведь <tex> p[x] = dp[x][0] = dp[y][0] = p[y] </tex>. | ||
− | Оценим время работы. Заметим, что найденные <tex> k </tex> строго убывают. Во-первых, потому что мы находим на каждом шаге максимальное значение <tex> k </tex>, а во-вторых, два раза подряд мы одно и то же <tex> k </tex> получить не можем, так как тогда получилось бы, что можно пройти <tex> 2 ^ k + 2 ^ k = 2 ^ {k + 1}</tex> шагов, а значит вместо первого <tex> k </tex>, мы бы нашли <tex> k + 1 </tex>. А значит всего <tex> O(\log{n}) </tex> значений <tex> k </tex>, их можно перебирать в порядке убывания. Сложность ответа на запрос <tex> O(\log{n}) </tex>. | + | Оценим время работы. Заметим, что найденные <tex> k </tex> строго убывают. Во-первых, потому что мы находим на каждом шаге максимальное значение <tex> k </tex>, а во-вторых, два раза подряд мы одно и то же <tex> k </tex> получить не можем, так как тогда получилось бы, что можно пройти <tex> 2 ^ k + 2 ^ k = 2 ^ {k + 1}</tex> шагов, а значит вместо первого <tex> k </tex>, мы бы нашли <tex> k + 1 </tex>. А, значит, всего <tex> O(\log{n}) </tex> значений <tex> k </tex>, их можно перебирать в порядке убывания. Сложность ответа на запрос <tex> O(\log{n}) </tex>. |
==Псевдокод== | ==Псевдокод== | ||
− | + | '''function''' preprocess(): | |
− | + | '''int[]''' p = dfs(0) | |
− | + | '''for''' i = 1 '''to''' n | |
− | + | dp[i][0] = p[i] | |
− | + | '''for''' j = 1 '''to''' log(n) | |
− | + | '''for''' i = 1 '''to''' n | |
− | + | dp[i][j] = dp[dp[i][j - 1]][j - 1] | |
− | + | ||
+ | '''int''' lca('''int''' v, '''int''' u): | ||
+ | '''if''' d[v] > d[u] | ||
+ | swap(v, u) | ||
+ | '''for''' i = log(n) '''downto''' 0 | ||
+ | '''if''' d[dp[u][i]] - d[v] >= 0 | ||
+ | u = dp[u][i] | ||
+ | '''if''' v == u | ||
+ | '''return''' v | ||
+ | '''for''' i = log(n) '''downto''' 0 | ||
+ | '''if''' dp[v][i] != dp[u][i] | ||
+ | v = dp[v][i] | ||
+ | u = dp[u][i] | ||
+ | '''return''' p[v] | ||
− | + | ==См. также== | |
− | + | * [[Сведение задачи LCA к задаче RMQ]] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
==Источники информации== | ==Источники информации== | ||
− | * [ | + | * [http://en.wikipedia.org/wiki/Lowest_common_ancestor Wikipedia: LCA] |
− | |||
* [http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor TopCoder tutorial: RMQ and LCA] | * [http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor TopCoder tutorial: RMQ and LCA] | ||
− | * [http://e-maxx.ru/algo/lca_simpler MAXimal :: algo :: Метод двоичного | + | * [http://e-maxx.ru/algo/lca_simpler MAXimal :: algo :: Метод двоичного подъёма ] |
− | |||
[[Категория: Алгоритмы и структуры данных]] | [[Категория: Алгоритмы и структуры данных]] | ||
[[Категория: Задача о наименьшем общем предке]] | [[Категория: Задача о наименьшем общем предке]] |
Текущая версия на 19:39, 4 сентября 2022
Метод двоичного подъёма — один из самых простых методов для решения задачи LCA в online. Он не использует метод решения задачи RMQ и основан на методе динамического программирования.
Содержание
Описание алгоритма
Как и большинство on-line алгоритмов для решения задачи LCA, этот метод делает сначала препроцессинг, чтобы потом отвечать на запросы.
Препроцессинг
Препроцессинг заключается в том, чтобы посчитать функцию:
— номер вершины, в которую мы придём если пройдём из вершины вверх по подвешенному дереву шагов, причём если мы пришли в корень, то мы там и останемся. Для этого сначала обойдем дерево в глубину, и для каждой вершины запишем номер её родителя и глубину вершины в подвешенном дереве . Если — корень, то . Тогда для функции есть рекуррентная формула:
Для того чтобы отвечать на запросы нам нужны будут только те значения
, где , ведь при больших значение будет номером корня.Всего состояний динамики
, где — это количество вершин в дереве. Каждое состояние считается за . Поэтому суммарная сложность времени и памяти препроцессинга — .Ответы на запросы
Ответы на запросы будут происходить за время
. Для ответа на запрос заметим сначала, что если , для некоторых и , то . Поэтому если , то пройдём от вершины на шагов вверх, это и будет новое значение и это можно сделать за . Можно записать число в двоичной системе, это представление этого число в виде суммы степеней двоек, и для всех пройти вверх последовательно из вершины в .Дальше считаем, что
.Если
, то ответ на запрос .А если
, то найдём такие вершины и , такие что , — предок , — предок и . Тогда ответом на запрос будет .Научимся находить эти вершины
и . Для этого сначала инициализируем и . Дальше на каждом шаге находим такое максимальное , что . И проходим из вершин и на шагов вверх. Если такого найти нельзя, то значения и , это те самые вершины, которые нам требуется найти, ведь .Оценим время работы. Заметим, что найденные
строго убывают. Во-первых, потому что мы находим на каждом шаге максимальное значение , а во-вторых, два раза подряд мы одно и то же получить не можем, так как тогда получилось бы, что можно пройти шагов, а значит вместо первого , мы бы нашли . А, значит, всего значений , их можно перебирать в порядке убывания. Сложность ответа на запрос .Псевдокод
function preprocess(): int[] p = dfs(0) for i = 1 to n dp[i][0] = p[i] for j = 1 to log(n) for i = 1 to n dp[i][j] = dp[dp[i][j - 1]][j - 1] int lca(int v, int u): if d[v] > d[u] swap(v, u) for i = log(n) downto 0 if d[dp[u][i]] - d[v] >= 0 u = dp[u][i] if v == u return v for i = log(n) downto 0 if dp[v][i] != dp[u][i] v = dp[v][i] u = dp[u][i] return p[v]