Префикс-функция
Рассмотрим строку
. Будем обозначать
подстроку строки
, которая начинается с
-го символа и заканчивается символом с индексом
. Префикс-функцию от строки
определим с помощью массива, который будем обозначать
.
– это длина наибольшего префикса подстроки
, совпадающего с ее суффиксом (но не равного самой подстроке
).
Алгоритм вычисления
Заметим сначала, что
превосходит
не более чем на единицу, т.к. в противном случае в качестве значения
мы бы должны были взять число
. Рассмотрим шаг алгоритма с номером
. Мы уже знаем значение
, обозначим его за
. Eсли
совпадает с
, то
, и можно переходить к следующему шагу алгоритма:
.
где

– символ строки,

– символы которые нужно сравнить (префикс и суффикс могут «накладываться» друг на друга). Если же
, то нужно рассмотреть префикс длины
. Предположим, что это не так, то есть, что существует префикс подстроки

длины
, такой что

и совпадающий с суффиксом длины
:
.
Это невозможно, т.к. в качестве значения

мы должны были бы взять
. Аналогично, если длина

не подходит, то проверять нужно
, и т.д.
Реализация алгоритма на языке C++:
std::vector<int> pf(s.size() + 1, 0);
for (int i = 2; i < (int)pf.size(); i++) {
int prefLen = pf[i - 1];
while (true) {
if (s[i - 1] == s[prefLen]) {
pf[i] = prefLen + 1;
break;
} else if (prefLen == 0) {
break;
}
prefLen = pf[prefLen];
}
}
На каждом шаге внешнего цикла значение префикс-функции увеличивается не более чем на единицу, поэтому
. На каждом шаге внутреннего цикла значение
уменьшается не менее, чем на единицу. Отсюда следует, что суммарное количество итераций внутреннего цикла ограничено числом итераций внешнего. Таким образом, сложность алгоритма можно оценить как
.
Задача о количестве различных подстрок
Приведем пример задачи, которая решается с помощью префикс-функции. Пусть
– это некоторая строка. Требуется найти количество ее различных подстрок.
Будем решать задачу с помощью динамического программирования. На каждом шаге алгоритма будем добавлять в строку по символу из
и искать количество получившихся новых подстрок. Рассмотрим шаг алгоритма с номером
. Пусть
– текущая строка, то есть
. Нам нужно найти количество новых подстрок, которые заканчиваются символом
. Для этого «перевернем» строку
и обозначим ее
. Таким образом, задача сводится к поиску суффиксов подстрок
, совпадающих с ее префиксом, который теперь начинается с нового символа
. Заметим, что достаточно найти только максимальный такой суффикс, т.к. все меньшие суффиксы будут в нем содержаться. Соответственно, количество новых подстрок, полученных на данном шаге, можно будет найти по формуле
.
где

– это префикс-функция, вычисленная для
. Пусть

– это длина строки
. Тогда мы

раз будем добавлять по символу и каждый раз заново вычислять префикс-функцию. Значит сложность алгоритма составит
.
Реализация решения задачи на языке C++:
int nUniqueSubstrings = 0;
std::vector<int> pf(s.length() + 1, 0);
std::string currentString = "";
for (int i = 0; i < (int)s.length(); i++) {
currentString += s[i];
std::string reversedCurrentString = currentString;
std::reverse(reversedCurrentString.begin(), reversedCurrentString.end());
pf[1] = 0;
for (int j = 2; j <= i + 1; j++) {
int prefLen = pf[j - 1];
while (true) {
if (reversedCurrentString[j - 1] == reversedCurrentString[prefLen]) {
pf[j] = prefLen + 1;
break;
} else if (prefLen == 0) {
pf[j] = 0;
break;
}
prefLen = pf[prefLen];
}
}
int maxLength = 0;
for (int j = 2; j <= i + 1; j++) {
maxLength = std::max(maxLength, pf[j]);
}
nUniqueSubstrings += i + 1 - maxLength;
}