Дерево палиндромов
Дерево палиндромов (англ. palindromic tree) — структура данных, позволяющая решить некоторые интересные задачи на палиндромы.
Эту структуру данных придумал Михаил Рубинчик[1] и рассказал ее на летних сборах в Петрозаводске в 2014 году.
Описание структуры
Дерево палиндромов состоит из вершин, каждая из которых соответствует палиндрому. Все вершины соответствуют разным палиндромам. Через будем обозначать строку, которой соответствует вершина . Ребра дерева палиндромов ориентированные и помечены символами. Ребро с символом ведет из вершины в вершину тогда и только тогда, когда .
Стоит обратить внимание на то, что название структуры данных выбрано не совсем удачно. На самом деле структура представляет из себя два дерева — одно для палиндромов четной длины, другое для палиндромов нечетной длины. Обозначим корни этих деревьев за и соответственно.
Помимо вершин и ребер в дереве палиндромов также присутствуют суффиксные ссылки. Для каждой вершины
ее суффиксная ссылка ведет в такую вершину , что является наибольшим суффиксом строки относительно других вершин. При этом важно понимать, что суффиксная ссылка из вершины одного дерева может вести как в то же, так и в другое дерево.
При реализации в целях экономии памяти мы не будем хранить для каждой вершины соответствующую ей строку-палиндром. Этот подход был бы неэффективным с точки зрения памяти. Вместо этого мы будем хранить только длину палиндрома (и для некоторых задач позицию палиндрома в строке).
Итак, структура будет состоять из двух деревьев и, соответственно, двух корней. Для удобства реализации каждый корень будет соответствовать фиктивной строке.
- будет соответствовать палиндрому длины . Это нужно для того, чтобы не обрабатывать отдельно случай добавления палиндрома длины 1. Теперь каждый раз при добавлении ребра из вершины к вершине , мы будем просто считать что .
- будет соответствовать фиктивному палиндрому длины 0.
Суффиксные ссылки обоих корней будут вести к вершине
.Построение
Утверждение: |
Строка — это единственная подстрока-палиндром строки , которой, возможно, нет в (т.е. все другие подстроки-палиндромы есть). |
Заметим, что все новые подстроки-палиндромы, которых не было в | , должны оканчиваться на символ , и поэтому должны быть палиндромом-суффиксом строки . Из-за того, что — наибольший палиндром-суффикс строки , все остальные меньшие палиндромы-суффиксы этой строки уже есть в каком-то префиксе строки (т.к. для каждого суффикса палиндрома есть равный ему префикс) и, соответственно, уже есть в .
Таким образом, чтобы обработать очередной символ , нужно просто спуститься по суффиксным ссылкам строки до тех пор, пока мы не найдем подходящую строку (причем мы всегда можем найти такую строку, возможно длины , если очередная суффиксная ссылка будет вести в корень). Затем нужно проверить, есть ли уже ребро по символу из вершины, соответствующей , и если нет, добавить это ребро в новую вершину .
Теперь нужно добавить суффиксную ссылку из вершины
. Если эта вершина уже существовала до добавления символа , ничего делать не нужно — суффиксная ссылка итак указывает на правильную вершину. Иначе нужно найти наибольший палиндром-суффикс строки , который будет иметь вид , где — это некоторая строка, возможно, пустая. Следуя той же логике, которую мы использовали раньше, — это палиндром-суффикс строки и может быть достигнут из по суффиксным ссылкам.Оценка сложности
Память
Каждая вершина в дереве соответствует подпалиндрому, всего различных подпалиндромов в строке не более
(т.к. при добавлении очередного символа появляется не более одного нового палиндрома). Поэтому дерево палиндромов занимает памяти.Время
Чтобы оценить временную сложность алгоритма, нужно заметить, что по мере того, как мы обрабатываем строку символ за символом, левая граница наибольшего палиндрома-суффикса уже обработанной строки сдвигается только вправо. Очевидно, эта граница может двигаться вправо не более
, где — длина строки, для которой мы строим дерево. То же самое относится и к левой границе той строки, на которую ведет суффиксная ссылка вновь добавленной вершины.Таким образом, суммарное время работы построения алгоритма
.Применения
Число новых палиндромов, порождаемых очередным символом
Задача: |
Определить число новых палиндромов-подстрок при добавлении символа | в конец строки .
Например, при добавлении символа к строке , которая уже состоит из палиндромов , и , добавляется новый палиндром .
Мы знаем, что число новых подпалиндромов при добавлении символа
или . Так что решение задачи довольно простое — будем строить дерево палиндромов символ за символом и для каждого нового символа отвечать, был ли добавлен новый палиндром или нет (определить это можно, например, по тому, были ли добавлены новые вершины к структуре).Число подпалиндромов
Задача: |
Определить число подпалиндромов, которое содержится в данной строке | .
Например, строка имеет четыре подпалиндрома: дважды , и .
Для решения задачи построим дерево палиндромов для данной строки. При обработке очередного символа добавим к ответу все палиндромы, которые содержат этот новый символ. Заметим, что эти палиндромы — это новый максимальный палиндром-суффикс
, который содержит новый символ, и все полиндромы, достижимые из по суффиксным ссылкам. Для того чтобы быстро найти их количество будем хранить в каждой вершине дерева палиндромов длину цепочки суффиксных ссылок (включая саму вершину), а затем будем просто прибавлять к ответу это число для каждого по мере добавления новых символов.Эта задача также может быть решена алгоритмом Манакера за ту же асимптотику, однако данный алгоритм не может быть расширен для более широкого класса задач, в отличие от дерева палиндромов.
Число вхождений каждого подпалиндрома в строку
Задача: |
Необходимо найти число вхождений каждого подпалиндрома строки в нее саму. |
Чтобы решить эту задачу деревом палиндромов, нужно обратить внимание на то, что при добавлении нового символа увеличивается количество вхождений наибольшего палиндрома-суффикса , содержащего новый символ, и всех палиндромов, достижимых из по суффиксным ссылкам.
Для каждой вершины
дерева палиндромов будем хранить число вхождений строки в исходную строку (не обязательно актуальные данные) и число, которое необходимо добавить к числу вхождений всех потомков вершины . Назовем такую операцию добавления операцией релаксации. После того, как релаксация будет выполнена для всех предков вершины , можно будет считать, что посчитанное число вхождений соответствует действительности.Данный метод очень похож на метод, описанный в статье про реализацию массовых обновлений в деревьях отрезков.
Поиск рефрен-палиндрома
Задача: |
Для данной строки необходимо найти палиндром, произведение длины которого на количество вхождений в строку является максимальным. |
Для решения данной задачи применим тот же алгоритм, что и в прошлой задаче, а затем пройдем по всем вершинам дерева палиндромов и выберем подходящую.