Задача

Сколько прочтёт Хорспул

поиск в тексте предподсчёт замеры

Прежде чем ставить алгоритм поиска в свою программу, хочется знать, сколько работы он сделает на ваших данных, а не на чужом замере. Посчитайте это для алгоритма Хорспула — упрощённого Бойера — Мура, на котором держатся быстрые поиски в стандартных библиотеках.

Текст t длины n, образец p длины m. Окно — место, куда приложен образец; первое окно начинается в позиции 0. В окне с началом s алгоритм сравнивает p[m − 1] с t[s + m − 1], потом p[m − 2] с t[s + m − 2] и так справа налево, пока буквы равны; каждая проверка пары букв — одно сравнение. Если совпали все m букв, это вхождение. Потом, совпало или нет, окно сдвигается на d(c), где c = t[s + m − 1] — буква текста под последней клеткой окна:

  • если c встречается среди первых m − 1 букв образца, d(c) = m − 1 − j, где j — самая правая такая позиция;
  • если не встречается, d(c) = m.

Алгоритм останавливается, когда окно перестаёт помещаться в текст. Для каждого образца выведите, сколько окон он проверил, сколько сделал сравнений и сколько нашёл вхождений. Среди тестов — начало «Войны и мира» и перевода Мод, геном фага лямбда и пример из статьи Мура «HERE IS A SIMPLE EXAMPLE».

Вход

В первой строке — текст, от 1 до 100 000 знаков (UTF-8, без переводов строки; пробелы — обычные знаки). Во второй — число образцов q (1 ≤ q ≤ 20). В каждой из следующих q строк — образец, от 1 до 100 знаков. Позиции считаются в знаках, а не в байтах. Тесты такие, что на каждый уходит меньше миллиона сравнений.

Выход

Для каждого образца — строка из трёх чисел: окон, сравнений, вхождений.

Примеры

Пример 1

Вход
HERE IS A SIMPLE EXAMPLE
3
EXAMPLE
SIMPLE
E
Выход
5 15 1
5 14 1
24 24 5

Пример 2

Вход
abracadabra abracadabra
3
abra
cad
zzz
Выход
7 19 4
9 13 2
7 7 0

Ограничения

Время · Python
4000 мс
Время · Go
1000 мс
Память
256 МБ
Проверка
8 тестов

Разбор

Открывайте по ступеням: каждая следующая подсказывает больше. Можно сразу к решению — но задача, решённая с одной подсказкой, запоминается лучше.

1Подсказка 1

Это моделирование, а не поиск ответа хитрым способом: ответ и есть ход алгоритма. Осталось сделать его ровно так, как сказано, и быстро находить d(c).

2Подсказка 2

Таблицу сдвигов стройте один раз на образец: проходите первые m − 1 букв слева направо и записывайте d[p[j]] = m − 1 − j. Правые вхождения перезапишут левые. Чем грозит пройти все m букв?

3Идея

Последняя буква образца в таблицу не входит. Она и так стоит под последней клеткой окна; если бы она попала в таблицу, её сдвиг был бы 0, и окно, которое кончается этой буквой, никогда не сдвинулось бы. Нужна предыдущая копия этой буквы в образце — она и даёт верный сдвиг.

4Разбор

Таблица — словарь «буква → сдвиг» по первым m − 1 буквам, по умолчанию m. Дальше цикл по окнам: сравнения справа налево до первого несовпадения, подсчёт, сдвиг по букве под последней клеткой.

Посмотрите на ответы. На «Войне и мире» Хорспул делает примерно одно сравнение на окно, а окон тем меньше, чем длиннее образец: для «князь Василий» — около 6000 окон на 60 000 знаков. Однобуквенный образец «а» проверяет каждую позицию, как наивный поиск. А в седьмом тесте образец «b» и девятнадцать «a» на тексте из одних «a» делает почти 400 000 сравнений на 20 000 знаков: окно каждый раз сравнивает 19 совпадающих букв, спотыкается на «b» и сдвигается на одну позицию, потому что под последней клеткой снова «a». Наивный поиск здесь в двадцать раз быстрее: он начинает сравнение с «b».

4+Решение
5Сложность

Таблица — O(m). Поиск — O(n · m) в худшем случае, как в седьмом тесте, и около n/m окон на обычном тексте с большим алфавитом.

6Типичные ошибки
  • Таблица по всем m буквам. Последняя буква получает сдвиг 0, его «чинят» до 1, и окна, кончающиеся этой буквой, ползут по одной позиции. На «SIMPLE» в первом примере получается шесть окон вместо пяти.
  • Сдвиг по несовпавшей букве. Это правило Бойера — Мура, а не Хорспула: у Хорспула сдвиг всегда считается по букве под последней клеткой окна, даже если несовпадение случилось левее.
  • Байты вместо знаков. В UTF-8 русская буква — два байта, и окон и сравнений станет больше. Читайте строки как знаки (в Go — []rune).

Алгоритмы задачи