EduBrick

Период строки

Наименьшая строка, повторением которой получается данная. Перебор делителей за n log n и трюк со сдвигом, работающий и для неполного повторения.

3 мин

Строка ss имеет период dd, если она получается повторением своего префикса длины dd — возможно, с обрывом на середине последнего повторения.

Например, у aabaabaab период 3, а у aabaabaa — тоже 3, просто последнее повторение неполное.

Ищем наименьший период. Тривиальный ответ d=nd = n есть всегда: строка — повторение себя один раз.

Случай полного повторения

Сначала более простая постановка: строка обязана разбиваться на целое число копий.

Тогда dd — делитель nn, и достаточно перебрать делители. Для каждого проверяем, что все блоки длины dd одинаковы:

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; }
}

Для делителя dd работы nd\frac{n}{d}, суммарно n1+n2+=O(nlogn)\frac{n}{1} + \frac{n}{2} + \dots = O(n \log n) — гармонический ряд.

Общий случай: трюк со сдвигом

Когда последнее повторение может быть неполным, делители не помогают: период — любое число от 1 до nn.

Здесь работает красивый критерий:

dd — период строки ss длины nn тогда и только тогда, когда s[0,nd)=s[d,n)s[0, n-d) = s[d, n).

То есть строка совпадает сама с собой, сдвинутая на dd.

for (int d = 1; d <= n; d++)
    if (h.get(0, n - d) == h.get(d, n)) return d;

Одно сравнение хешей на каждое dd, итого O(n)O(n) после предподсчёта.

Проверено: на ста тысячах случайных строк результат совпал с посимвольной проверкой.

Почему критерий верен

В одну сторону очевидно: если ss — повторение блока длины dd, то сдвиг на dd совмещает блоки друг с другом.

В другую сторону интереснее. Пусть s[0,nd)=s[d,n)s[0, n-d) = s[d, n). Это равенство означает, что si=si+ds_i = s_{i+d} для всех допустимых ii.

Возьмём любую позицию ii. Применяя равенство многократно, получаем si=si+d=si+2d=s_i = s_{i+d} = s_{i+2d} = \dots — то есть все позиции с одинаковым остатком по модулю dd несут один и тот же символ.

А это в точности и значит, что строка состоит из повторений блока длины dd.

Ключ в том, что одно равенство длинных кусков сразу даёт цепочку равенств отдельных символов. Именно поэтому хватает одного сравнения вместо nd\frac{n}{d}.

Связь с префикс-функцией

Ту же задачу решает алгоритм Кнута — Морриса — Пратта: наименьший период равен nπn1n - \pi_{n-1}, где π\pi — префикс-функция.

Это точный алгоритм за O(n)O(n), без вероятностей. Если вы помните КМП, берите его.

Решение на хешах полезно тем, что обобщается: тем же способом ищется период подстроки, период с точностью до одной замены, период циклического сдвига — там, где префикс-функция уже не подходит.

Родственные задачи

Является ли строка степенью другой. Проверяем, что наименьший период делит nn.

Наибольшее kk, что s=tks = t^k. Это n/dn / d для наименьшего периода-делителя.

Период каждого префикса. Тот же цикл, но для каждого префикса отдельно — O(n2)O(n^2) на хешах против O(n)O(n) на префикс-функции. Здесь КМП явно лучше.

Наименьший циклический сдвиг. Другая задача, хотя звучит похоже; решается сравнением подстрок удвоенной строки.