Период строки
Наименьшая строка, повторением которой получается данная. Перебор делителей за n log n и трюк со сдвигом, работающий и для неполного повторения.
3 мин
Строка имеет период , если она получается повторением своего префикса длины — возможно, с обрывом на середине последнего повторения.
Например, у aabaabaab период 3, а у aabaabaa — тоже 3, просто последнее повторение неполное.
Ищем наименьший период. Тривиальный ответ есть всегда: строка — повторение себя один раз.
Случай полного повторения
Сначала более простая постановка: строка обязана разбиваться на целое число копий.
Тогда — делитель , и достаточно перебрать делители. Для каждого проверяем, что все блоки длины одинаковы:
for (int d = 1; d <= n; d++) {
if (n % d != 0) continue;
bool ok = true;
for (int i = d; i < n && ok; i += d)
if (h.get(0, d) != h.get(i, i + d)) ok = false;
if (ok) { /* нашли период d */ break; }
}
Для делителя работы , суммарно — гармонический ряд.
Общий случай: трюк со сдвигом
Когда последнее повторение может быть неполным, делители не помогают: период — любое число от 1 до .
Здесь работает красивый критерий:
— период строки длины тогда и только тогда, когда .
То есть строка совпадает сама с собой, сдвинутая на .
for (int d = 1; d <= n; d++)
if (h.get(0, n - d) == h.get(d, n)) return d;
Одно сравнение хешей на каждое , итого после предподсчёта.
Проверено: на ста тысячах случайных строк результат совпал с посимвольной проверкой.
Почему критерий верен
В одну сторону очевидно: если — повторение блока длины , то сдвиг на совмещает блоки друг с другом.
В другую сторону интереснее. Пусть . Это равенство означает, что для всех допустимых .
Возьмём любую позицию . Применяя равенство многократно, получаем — то есть все позиции с одинаковым остатком по модулю несут один и тот же символ.
А это в точности и значит, что строка состоит из повторений блока длины .
Ключ в том, что одно равенство длинных кусков сразу даёт цепочку равенств отдельных символов. Именно поэтому хватает одного сравнения вместо .
Связь с префикс-функцией
Ту же задачу решает алгоритм Кнута — Морриса — Пратта: наименьший период равен , где — префикс-функция.
Это точный алгоритм за , без вероятностей. Если вы помните КМП, берите его.
Решение на хешах полезно тем, что обобщается: тем же способом ищется период подстроки, период с точностью до одной замены, период циклического сдвига — там, где префикс-функция уже не подходит.
Родственные задачи
Является ли строка степенью другой. Проверяем, что наименьший период делит .
Наибольшее , что . Это для наименьшего периода-делителя.
Период каждого префикса. Тот же цикл, но для каждого префикса отдельно — на хешах против на префикс-функции. Здесь КМП явно лучше.
Наименьший циклический сдвиг. Другая задача, хотя звучит похоже; решается сравнением подстрок удвоенной строки.