Гранди на деревьях
Одна и та же картинка задаёт разные игры. Два правила вычисления, и путать их дорого.
3 мин
Дерево в условии задачи про игру встречается постоянно, а правило вычисления зависит не от него, а от того, что делает ход.
Ход двигает фишку
Фишка стоит в вершине, ход переставляет её в ребёнка. Позиция — это вершина, ходы из неё ведут в детей, и работает обычное определение:
Листья получают ноль. Если фишек несколько, позиция — сумма игр, и значение равно XOR значений их вершин.
Ход рубит ребро
Ход разрубает ребро, и всё, что отделилось от корня, отваливается. Теперь ветки разных детей независимы: рубка в поддереве одного ребёнка не касается другого. Значит, позиция — сумма игр, и
Единица — это само ребро в ребёнка: его тоже можно разрубить, и тогда ветка исчезнет целиком. Строгая формулировка: стебель длины 1 с прикреплённой игрой ценности имеет ценность . В теории Hackenbush это «принцип двоеточия».
for (int i = n - 1; i >= 0; i--) { // порядок, обратный обходу
int v = order[i], value = 0;
for (int c : children[v]) value ^= grundy[c] + 1;
grundy[v] = value;
}
Обход пишите без рекурсии. Цепочка из ста тысяч вершин — обычное дело, а стек кончается на десятках тысяч.
Чем эти правила отличаются на практике
| двигаем фишку | рубим ребро | |
|---|---|---|
| формула | mex по детям | XOR из |
| лист | ||
| цепочка длины от корня | или по чётности | |
| звезда из лучей |
Последняя строка — хороший тест: если ваша программа на звезде из трёх лучей даёт единицу в игре с рубкой, правило перепутано.
Прибавление единицы не XOR-линейно
Соблазнительно думать, что ведёт себя как XOR с константой, и построить на этом быстрый пересчёт «а что будет, если значение поддерева изменится». Это неверно:
Поэтому задачи вида «посчитать все выигрышные рубки» не решаются одним числом на вершину: изменение приходится честно поднимать по пути к корню. Это , и ограничения в таких задачах обычно маленькие именно поэтому.
Перевешивание за другой корень
Если нужен ответ для каждого корня, считать заново раз — . Стандартный приём: посчитать значения при корне в первой вершине, а потом спуститься от корня, поддерживая «значение вершины со стороны родителя»:
здесь пробегает остальных детей , а — вклад всего, что висит на сверху. XOR по всем детям, кроме одного, берётся как «общий XOR, из которого убрали слагаемое» — XOR сам себе обратен, поэтому убрать легко. Ответ для корня — это .
Смежное: функция Гранди, сумма игр, принцип слияния.