Игра Уайтхоффа
Две кучи, ход — взять из одной или поровну из обеих. Проигрышные позиции задаются золотым сечением.
3 мин
Две кучи размеров и . Ход — взять любое положительное число камней из одной кучи либо поровну из обеих. Проигрывает тот, кто не может сходить, то есть тот, кому досталась позиция .
Та же игра часто описывается как ферзь на доске: фигура ходит влево, вниз и по диагонали влево-вниз, цель — угол. Координаты клетки и есть размеры куч.
Проигрышные позиции
Первые несколько (с точностью до перестановки):
Закономерность видно по разностям: они идут подряд. А меньшие числа пар — это : в точности те, которые ещё не встречались.
Отсюда способ построить таблицу без всякой теории: идти по , брать наименьшее неиспользованное число и объявлять пару проигрышной. Для этого достаточно.
Формула
Те же пары выражаются через золотое сечение :
Проверено перебором: на всей доске множество проигрышных клеток совпало с формулой без исключений.
Как проверять позицию, не строя таблицу
Пусть и . Позиция проигрышна ровно тогда, когда .
Считать можно двумя способами, и стоит знать границы обоих.
Целочисленный — точен всегда, но упирается в разрядность: в произведении при порядка кончается 64-битное знаковое число.
long long isqrt(long long n) { // целочисленный корень
long long x = (long long)std::sqrt((double)n);
while (x > 0 && x * x > n) x--; // подгонка обязательна:
while ((x + 1) * (x + 1) <= n) x++; // double промахивается на единицу
return x;
}
bool losing(long long a, long long b) {
if (a > b) std::swap(a, b);
long long k = b - a;
return a == (k + isqrt(5 * k * k)) / 2;
}
Через double — просто (long long)(k * PHI). Здесь ожидаемо подозрение на потерю точности, поэтому вот замер: сравнение с точной формулой на 300 000 подряд идущих в каждом диапазоне.
| около | промахов из 300 000 |
|---|---|
| 0 | |
| 0 | |
| 0 | |
| 1 | |
| 53 | |
| 53 796 |
Вывод получается не тот, которого ждёшь: до double не ошибается ни разу, а ломаться начинает там, где целочисленный вариант уже переполняется. Практически это значит, что при ограничениях до годятся оба, а за пределами не годится ни один — там нужна 128-битная арифметика.
И всё же писать лучше целочисленный: он не требует ни этого замера, ни рассуждений о том, сколько разрядов осталось на дробную часть.
Почему нельзя просто сложить две кучи
Уайтхофф — не сумма двух игр: ход «взять поровну из обеих» меняет оба слагаемых сразу, а теорема Шпрага — Гранди такого не допускает. Поэтому XOR здесь неверен, и приходится разбирать игру отдельно.
Это полезный пример: как только в условии появляется ход, задевающий несколько частей позиции, разложение на сумму игр надо перепроверять.