Полиномиальное хеширование строк: как выбрать основание и модуль, чтобы не словить коллизию?
Хочу сравнивать подстроки за O(1) через полиномиальный хеш. Какую брать базу p и модуль mod? Видел в разных решениях 31, 131, 1e9+7, 2^61-1. Какие из них безопасны на олимпиаде, где жюри может подложить анти-хеш тест?
2 ответа
Хеш строки — это значение многочлена h = Σ s[i] * base^i mod M. Правила выбора:
-
База должна быть больше размера алфавита и взаимно проста с модулем. Для строчных латинских
base = 131или137(а не 31 — слишком мала, выше шанс коллизий на коротких алфавитах). Лучше брать случайную базу в диапазоне, скажем,[256, M)при старте программы — это ломает заранее заготовленные анти-хеш тесты. -
Модуль — большое простое.
1e9+7работает, но при сравнении ~10^5 подстрок вероятность коллизии по парадоксу дней рождения уже заметна (~q^2 / M). Поэтому либо двойное хеширование (два разных модуля), либо один модуль2^61 - 1(Mersenne) — он почти 2^61, и одиночная коллизия крайне маловероятна.
const uint64_t M = (1ULL << 61) - 1; // простое Мерсенна
uint64_t base; // выбрать случайно при старте
uint64_t mulmod(uint64_t a, uint64_t b) { // умножение по mod 2^61-1
__uint128_t c = ( __uint128_t)a * b;
uint64_t lo = (uint64_t)(c & M), hi = (uint64_t)(c >> 61);
uint64_t r = lo + hi;
return r >= M ? r - M : r;
}
Препроцессинг префиксных хешей — O(n), сравнение любой пары подстрок потом — O(1).
Главный практический совет: рандомизируйте базу через mt19937_64 с сидом от chrono::high_resolution_clock. Фиксированная база типа 131 регулярно выбивается на Codeforces заготовленными тестами (есть генераторы строк-коллизий под популярные базы). Случайная база + модуль 2^61-1 — и почти любая хеш-задача проходит без двойного хеша.