Палиндром — это фраза, которая читается одинаково слева направо и справа налево. Например:
-
«was it a car or a cat I saw?»
-
«а роза упала на лапу Азора»
-
«abacaba» (палиндром нечётной длины)
-
«abba» (палиндром чётной длины)
Для простоты, мы будем рассматривать только последовательности из строчных латинских букв.
Палиндромы — не самые часто встречающиеся в реальной жизни объекты, однако задачи на палиндромы любят давать на соревнованиях по спортивному программированию. В этой статье мы опишем эффективные способы их представления.
Пусть есть строка
Мы сразу сталкиваемся с очевидной трудностью: их в строке может быть
Наивное решение — перебрать
vector<int> pal_array(string s) {
int n = s.size();
// окружим строку спецсимволами, чтобы не рассматривать выход за границы
s = "#" + s + "$";
// в этом массиве будем хранить расстояние от центра до границы палиндрома
vector<int> t(n, 0);
for(int i = 1; i <= n; i++)
while (s[i - t[i - 1]] == s[i + t[i - 1]])
r[i-1]++;
return r;
}
Тот же пример
Для оптимизации применим идею, знакомую из алгоритма z-функции: при инициализации
vector<int> manacher_odd(string s) {
int n = (int) s.size();
vector<int> d(n, 1);
int l = 0, r = 0;
for (int i = 1; i < n; i++) {
if (i < r)
d[i] = min(r - i + 1, d[l + r - i]);
while (i - d[i] >= 0 && i + d[i] < n && s[i - d[i]] == s[i + d[i]])
d[i]++;
if (i + d[i] - 1 > r)
l = i - d[i] + 1, r = i + d[i] - 1;
}
return d;
}
Так же, как и z-функция, алгоритм работает за линейное время: цикл while
запускается только когда
Для случая чётных палиндромов меняется только индексация:
vector<int> manacher_even(string s) {
int n = (int) s.size();
vector<int> d(n, 0);
int l = -1, r = -1;
for (int i = 0; i < n - 1; i++) {
if (i < r)
d[i] = min(r - i, d[l + r - i - 1]);
while (i - d[i] >= 0 && i + d[i] + 1 < n && s[i - d[i]] == s[i + d[i] + 1])
d[i]++;
if (i + d[i] > r)
l = i - d[i] + 1, r = i + d[i];
}
return d;
}
Также можно было не писать отдельно две реализации, а воспользоваться следующим трюком — сделать замену:
Теперь нечётные палиндромы с центром в
Дерево палиндромов (англ. palindromic tree, EERTREE) — структура данных, использующая другой, более мощный формат хранения информации обо всех подпалиндромах, чем размеры
Лемма. В строке есть не более
Доказательство. Пусть мы дописываем к строке по одному символу и в данный момент, записав
Этот факт позволяет сопоставить всем палиндромам строки сопоставить следующую структуру: возьмём от каждого палиндрома его правую половину (например,
Наивный алгоритм построения будет в худшем случае работать за
Будем поддерживать наибольший суффикс-палиндром. Когда мы будем дописывать очередной символ
Для этого поступим аналогично алгоритму Ахо-Корасик: будем поддерживать для каждого палиндрома суффиксную ссылку
Если в подходящей вершине этого перехода не существовало, то нужно создать новую вершину, и для неё тоже понадобится своя суффиксная ссылка. Чтобы найти её, будем продолжать подниматься по суффиксным ссылкам предыдущего суффикс-палиндрома, пока не найдём второе такое место, которое мы можем дополнить символом
const int maxn = 1e5, k = 26;
int s[maxn], len[maxn], link[maxn], to[maxn][k];
int n, last, sz;
void init() {
s[n++] = -1;
link[0] = 1;
len[1] = -1;
sz = 2;
}
int get_link(int v) {
while (s[n-len[v]-2] != s[n-1])
v = link[v];
return v;
}
void add_char(int c) {
s[n++] = c;
last = get_link(last);
if (!to[last][c]) {
len[sz] = len[last] + 2;
link[sz] = to[get_link(link[last])][c];
to[last][c] = sz++;
}
last = to[last][c];
}
Здесь мы использовали обычный массив для хранения переходов. Как и для любых префиксных деревьев, вместо него можно использовтать бинарное дерево поиска, хэш-таблицу, односвязный список и другие структуры, позволяющие обменять время на память, немного изменив асимптотику.
Покажем линейность алгоритма. Рассмотрим длину наибольшего суффикс-палиндрома строки. Каждый новый символ увеличивает её не более, чем на 2. При этом каждый переход по суффиксной ссылке уменьшает её, поэтому нахождение первого суффикс-палиндрома амортизировано работает за линейное время.
Аналогичными рассуждениями о длине второго суффикс-палиндрома (его длина увеличивается тоже не более, чем на 2) получаем, что пересчёт суффиксных ссылок при создании новых вершин тоже суммарно работает за линейное время.