Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

e-maxx_algo

.pdf
Скачиваний:
135
Добавлен:
03.06.2015
Размер:
6.19 Mб
Скачать

клетка) изначально находится робот. Робот может двигаться только вправо или вверх, и в итоге он должен попасть

в клетку , избежав все препятствия. Требуется посчитать число путей, которыми он может это сделать. Предполагаем, что размеры и очень большие (скажем, до ), а количество — небольшое (порядка ).

Для решения сразу в целях удобства отсортируем препятствия в том порядке, в каком мы можем их обойти: т. е., например, по координате , а при равенстве — по координате .

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

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

Для этого можно, например, перебрать подмножество тех препятствий, на которые мы точно наступим, посчитать число способов сделать это (просто перемножив число способов дойти от стартовой клетки до первого из выбранных препятствий, от первого препятствия до второго, и так далее), и затем прибавить или отнять это число от ответа, в соответствии со стандартной формулой включений-исключений.

Однако это снова будет неполиномиальное решение — за асимптотику

. Покажем, как

 

получить полиномиальное решение.

 

 

Решать будем динамическим программированием: научимся вычислять числа

— число

способов дойти от -ой точки до -ой, не наступив при этом ни на одно препятствие (кроме самих

и ,

естественно). Всего у нас будет точки, поскольку к препятствиям добавляются стартовая и конечная клетки.

Если мы на секунду забудем про все препятствия и просто посчитаем число путей из клетки в клетку , то тем самым мы учтём некоторые "плохие" пути, проходящие через препятствия. Научимся считать количество этих "плохих"

путей. Переберём первое из препятствий

, на которое мы наступим, тогда количество путей будет

равно

, умноженному на число произвольных путей из в

. Просуммировав это по всем , мы

посчитаем количество "плохих" путей.

 

 

Таким образом, значение

мы научились считать за время

. Следовательно, решение всей задачи

имеет асимптотику

.

 

 

Число взаимно простых четвёрок

Дано чисел: . Требуется посчитать количество способов выбрать из них четыре числа так, что их совокупный наибольший общий делитель равен единице.

Будем решать обратную задачу — посчитаем число "плохих" четвёрок, т.е. таких четвёрок, в которых все числа делятся на число .

Воспользуемся формулой включений-исключений, суммируя количество четвёрок, делящихся на делитель (но, возможно, делящихся и на больший делитель):

где — это количество простых в факторизации числа , — количество четвёрок, делящихся на .

Чтобы посчитать функцию , надо просто посчитать количество чисел, кратных , и биномиальным коэффициентом посчитать число способов выбрать из них четвёрку.

Таким образом, с помощью формулы включений-исключений мы суммируем количество четвёрок, делящихся на простые числа, затем отнимаем число четвёрок, делящихся на произведение двух простых, прибавляем четвёрки, делящиеся на три простых, и т.д.

Число гармонических троек

Дано число

. Требуется посчитать число таких троек чисел

, что они

являются гармоническими тройками, т.е.:

 

 

 

либо

 

 

,

 

либо

,

,

.

 

Во-первых, сразу перейдём к обратной задаче — т.е. посчитаем число негармонических троек.

Во-вторых, заметим, что в любой негармонической тройке ровно два её числа находятся в такой ситуации, что это число взаимно просто с одним числом тройки и не взаимно просто с другим числом тройки.

Таким образом, количество негармонических троек равно сумме по всем числам от до произведений

количества взаимно простых с текущим числом чисел на количество не взаимно простых чисел.

Теперь всё, что нам осталось для решения задачи — это научиться считать для каждого числа в отрезке

количество чисел, взаимно простых (или не взаимно простых) с ним. Хотя эта задача уже рассматривалась нами выше, описанное выше решение не подходит здесь — оно потребует факторизации каждого из чисел от до , и затем перебора всевозможных произведений простых чисел из факторизации.

Поэтому нам понадобится более быстрое решение, которое подсчитывает ответы для всех чисел из отрезка сразу. Для этого можно реализовать такую модификацию решета Эратосфена:

Во-первых, нам надо найти все числа в отрезке , в факторизации которых никакое простое не входит дважды.

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

Для этого нам надо завести массивы , хранящие для каждого числа количество простых в его факторизации, и — содержащий для каждого числа или — все простые входят в него в степени или нет.

После этого во время решета Эратосфена при обработке очередного простого числа мы пройдёмся по всем

числам, кратным текущему числу, и увеличим

у них, а у всех чисел, кратных квадрату от текущего простого

— поставим

.

 

 

Во-вторых, нам надо посчитать ответ для всех чисел от до , т.е. массив

— количество чисел, не

взаимно простых с данным.

 

 

 

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

Итак, пусть у нас есть число , для которого

, т.е. это число, участвующее в формуле

включений-исключений. Переберём все числа, кратные

, и к ответу

каждого из таких чисел мы должны

прибавить или вычесть величину

. Знак — прибавление или вычитание — зависит от

: если

нечётна, то надо прибавлять, иначе вычитать.

Реализация:

int n;

bool good[MAXN];

int deg[MAXN], cnt[MAXN];

long long solve() {

memset (good, 1, sizeof good); memset (deg, 0, sizeof deg); memset (cnt, 0, sizeof cnt);

long long ans_bad = 0;

for (int i=2; i<=n; ++i) { if (good[i]) {

if (deg[i] == 0) deg[i] = 1; for (int j=1; i*j<=n; ++j) {

if (j > 1 && deg[i] == 1)

 

 

if (j % i == 0)

 

 

good[i*j] = false;

 

 

else

 

 

++deg[i*j];

 

}

cnt[i*j] += (n / i) * (deg[i]%2==1 ? +1 : -1);

 

 

 

}

 

 

ans_bad += (cnt[i] - 1) * 1ll * (n-1 - cnt[i]);

 

}

 

}

return (n-1) * 1ll * (n-2) * (n-3) / 6 - ans_bad / 2;

 

 

Асимптотика такого решения составляет

, поскольку почти для каждого числа оно совершает

примерно

итераций вложенного цикла.

 

Число перестановок без неподвижных точек

Докажем, что число перестановок длины без неподвижных точек равно следующему числу:

и приблизительно равно числу:

(более того, если округлить это выражение к ближайшему целому — то получится в точности число перестановок без неподвижных точек)

Обозначим через множество перестановок длины с неподвижной точкой в позиции (). Воспользуемся теперь формулой включений-исключений, чтобы посчитать число перестановок хотя бы с

одной неподвижной точкой. Для этого нам надо научиться считать размеры множеств-пересечений

, они

выглядят следующим образом:

 

 

 

 

 

поскольку если мы знаем, что число неподвижных точек равно , то тем самым мы знаем позицию элементов перестановки, а все остальные элементов могут стоять где угодно.

Подставляя это в формулу включений-исключений и учитывая, что число способов выбрать подмножество размера из -элементного множества равно , получаем формулу для числа перестановок хотя бы с одной неподвижной точкой:

Тогда число перестановок без неподвижных точек равно:

Упрощая это выражение, получаем точное и приблизительное выражения для количества перестановок без неподвижных точек:

(поскольку сумма в скобках — это первые членов разложения в ряд Тейлора )

В заключение стоит отметить, что аналогичным образом решается задача, когда требуется, чтобы неподвижных точек не было среди первых элементов перестановок (а не среди всех, как мы только что решали). Формула получится такая, как приведённая выше точная формула, только в ней сумма будет идти до , а не до .

Задачи в online judges

Список задач, которые можно решить, используя принцип включений-исключений:

UVA #10325 "The Lottery" [сложность: низкая]

 

UVA #11806 "Cheerleaders"

[сложность: низкая]

TopCoder SRM 477 "CarelessSecretary" [сложность: низкая]

TopCoder TCHS 16 "Divisibility"

[сложность: низкая]

SPOJ #6285 NGM2 "Another Game With Numbers" [сложность: низкая]

TopCoder SRM 382

"CharmingTicketsEasy"

[сложность: средняя]

TopCoder SRM 390

"SetOfPatterns"

[сложность: средняя]

TopCoder SRM 176

"Deranged"

[сложность: средняя]

TopCoder SRM 457

"TheHexagonsDivOne"

[сложность: средняя]

SPOJ #4191 MSKYCODE "Sky Code"

[сложность: средняя]

SPOJ #4168 SQFREE "Square-free integers"

[сложность: средняя]

CodeChef "Count Relations"

[сложность: средняя]

Литература

Debra K. Borkovitz. "Derangements and the Inclusion-Exclusion Principle"

Игры на произвольных графах

Пусть игра ведётся двумя игроками на некотором графе G. Т.е. текущее состояние игры - это некоторая вершина графа, и из каждой вершины рёбра идут в те вершины, в которые можно пойти следующим ходом.

Мы рассматриваем самый общий случай - случай произвольного ориентированного графа с циклами. Требуется для заданной начальной позиции определить, кто выиграет при оптимальной игре обоих игроков (или определить, что результатом будет ничья).

Мы решим эту задачу очень эффективно - найдём ответы для всех вершин графа за линейное относительно количества рёбер время - O (M).

Описание алгоритма

Про некоторые вершины графа заранее известно, что они являются выигрышными или проигрышными; очевидно, такие вершины не имеют исходящих рёбер.

Имеем следующие факты:

если из некоторой вершины есть ребро в проигрышную вершину, то эта вершина выигрышная;

если из некоторой вершины все рёбра исходят в выигрышные вершины, то эта вершина проигрышная;

если в какой-то момент ещё остались неопределённые вершины, но ни одна из них не подходят ни под первое, ни под второе правило, то все эти вершины - ничейные.

Таким образом, уже ясен алгоритм, работающий за асимптотику O (N M) - мы перебираем все вершины, пытаемся

к каждой применить первое либо второе правило, и если мы произвели какие-то изменения, то повторяем всё заново. Однако этот процесс поиска и обновления можно значительно ускорить, доведя асимптотику до линейной.

Переберём все вершины, про которые изначально известно, что они выигрышные или проигрышные. Из каждой из них пустим следующий поиск в глубину. Этот поиск в глубину будет двигаться по обратным рёбрам. Прежде всего, он не будет заходить в вершины, которые уже определены как выигрышные или проигрышные. Далее, если поиск в глубину пытается пойти из проигрышной вершины в некоторую вершину, то её он помечает как выигрышную, и идёт в неё. Если же поиск в глубину пытается пойти из выигрышной вершины в некоторую вершину, то он должен проверить, все ли рёбра ведут из этой вершины в выигрышные. Эту проверку легко осуществить за O (1), если в каждой вершине будем хранить счётчик рёбер, которые ведут в выигрышные вершины. Итак, если поиск в глубину пытается пойти из выигрышной вершины в некоторую вершину, то он увеличивает в ней счётчик, и если счётчик сравнялся

с количеством рёбер, исходящих из этой вершины, то эта вершина помечается как проигрышная, и поиск в глубину идёт

вэту вершину. Иначе же, если целевая вершина так и не определена как выигрышная или проигрышная, то поиск

вглубину в неё не заходит.

Итого, мы получаем, что каждая выигрышная и каждая проигрышная вершина посещается нашим алгоритмом ровно один раз, а ничейные вершины и вовсе не посещаются. Следовательно, асимптотика действительно O (M).

Реализация

Рассмотрим реализацию поиска в глубину, в предположении, что граф игры построен в памяти, степени исхода посчитаны и записаны в degree (это будет как раз счётчиком, он будет уменьшаться, если есть ребро в выигрышную вершину), а также изначально выигрышные или проигрышные вершины уже помечены.

vector<int> g [100]; bool win [100];

bool loose [100]; bool used[100]; int degree[100];

void dfs (int v) { used[v] = true;

for (vector<int>::iterator i = g[v].begin(); i != g[v].end(); ++i) if (!used[*i]) {

if (loose[v])

win[*i] = true; else if (--degree[*i] == 0)

loose[*i] = true;

else

continue; dfs (*i);

}

}

Пример задачи. "Полицейский и вор"

Чтобы алгоритм стал более ясным, рассмотрим его на конкретном примере.

Условие задачи. Имеется поле размером MxN клеток, в некоторые клетки заходить нельзя. Известны начальные координаты полицейского и вора. Также на карте может присутствовать выход. Если полицейский окажется в одной клетке с вором, то выиграл полицейский. Если же вор окажется в клетке с выходом (и в этой клетке не стоит полицейский), то выиграет вор. Полицейский может ходить в 8 направлениях, вор - только в 4 (вдоль осей координат). И полицейский, и вор могут пропустить свой ход. Первым ход делает полицейский.

Построение графа. Построим граф игры. Мы должны формализовать правила игры. Текущее состояние игры определяется координатами полицейского P, вора T, а также булева переменная Pstep, которая определяет, кто будет делать следующий ход. Следовательно, вершина графа определена тройкой (P,T,Pstep). Граф построить легко, просто соответствуя условию.

Далее нужно определить, какие вершины являются выигрышными или проигрышными изначально. Здесь есть тонкий момент. Выигрышность/проигрышность вершины помимо координат зависит и от Pstep - чей сейчас ход. Если сейчас ход полицейского, то вершина выигрышная, если координаты полицейского и вора совпадают; вершина проигрышная, если она не выигрышная и вор находится на выходе. Если же сейчас ход вора, то вершина выигрышная, если вор находится на выходе, и проигрышная, если она не выигрышная и координаты полицейского и вора совпадают.

Единственный момент, которой нужно решить - строить граф явно или делать это "на ходу", прямо в поиске в глубину. С одной стороны, если строить граф предварительно, то будет меньше вероятность ошибиться. С другой стороны, это увеличит объём кода, да и время работы будет в несколько раз медленнее, чем если строить граф "на ходу".

Реализация всей программы:

struct state { char p, t; bool pstep;

};

vector<state> g [100][100][2];

// 1 = policeman coords; 2 = thief coords; 3 = 1 if policeman's step or 0 if thief's.

bool win [100][100][2]; bool loose [100][100][2]; bool used[100][100][2]; int degree[100][100][2];

void dfs (char p, char t, bool pstep) { used[p][t][pstep] = true;

for (vector<state>::iterator i = g[p][t][pstep].begin(); i != g[p] [t][pstep].end(); ++i)

if (!used[i->p][i->t][i->pstep]) { if (loose[p][t][pstep])

win[i->p][i->t][i->pstep] = true; else if (--degree[i->p][i->t][i->pstep] == 0)

loose[i->p][i->t][i->pstep] = true;

else

continue;

dfs (i->p, i->t, i->pstep);

}

}

int main() {

int n, m;

cin >> n >> m; vector<string> a (n); for (int i=0; i<n; ++i)

cin >> a[i];

for (int p=0; p<n*m; ++p)

for (int t=0; t<n*m; ++t)

for (char pstep=0; pstep<=1; ++pstep) {

int px = p/m, py = p%m, tx=t/m, ty=t%m;

if (a[px][py]=='*' || a[tx][ty]=='*') continue;

 

bool & wwin = win[p][t][pstep];

 

 

 

bool & lloose = loose[p][t][pstep];

 

if (pstep)

 

lloose

= !wwin && a[tx][ty] == 'E';

wwin = px==tx && py==ty,

else

 

 

 

 

lloose

= !wwin && px==tx && py==ty;

wwin = a[tx][ty] == 'E',

if (wwin || lloose) continue;

 

 

 

 

 

 

state st = { p, t, !pstep };

 

 

 

g[p][t][pstep].push_back (st);

 

 

 

st.pstep = pstep != 0;

 

 

 

degree[p][t][pstep] = 1;

 

 

1, 1 };

const int dx[] = { -1, 0, 1, 0,

 

-1, -1,

const int dy[] = { 0, 1, 0, -1,

 

-1, 1, -

1, 1 };

 

for (int d=0; d<(pstep?8:4); ++d) {

 

 

int ppx=px, ppy=py, ttx=tx, tty=ty;

 

if (pstep)

ppy += dy[d];

 

ppx += dx[d],

 

else

tty += dy[d];

 

ttx += dx[d],

ppy<m && a[ppx][ppy]!='*' &&

if (ppx>=0 && ppx<n && ppy>=0 &&

ttx>=0 && ttx<n && tty>=0

&& tty<m && a[ttx][tty]!='*')

{

 

 

 

 

 

pstep].push_back (st);

g[ppx*m+ppy][ttx*m+tty][!

++degree[p][t][pstep];

 

 

}

 

 

}

}

 

 

 

 

 

for (int p=0; p<n*m; ++p)

 

 

for (int t=0; t<n*m; ++t)

 

 

for (char pstep=0; pstep<=1; ++pstep)

 

 

[pstep]) && !used[p][t][pstep])

if ((win[p][t][pstep] || loose[p][t]

dfs (p, t, pstep!=0);

 

 

 

 

 

int p_st, t_st;

 

 

 

for (int i=0; i<n; ++i)

 

 

 

for (int j=0; j<m; ++j)

 

 

if (a[i][j] == 'C')

 

 

else if

p_st = i*m+j;

 

 

(a[i][j] == 'T')

 

 

 

t_st = i*m+j;

 

 

cout << (win[p_st][t_st][true] ? "WIN" : loose[p_st][t_st] [true] ? "LOSS" : "DRAW");

}

Теория Шпрага-Гранди. Ним

Введение

Теория Шпрага-Гранди — это теория, описывающая так называемые равноправные (англ. "impartial") игры двух игроков, т.е. игры, в которых разрешённые ходы и выигрышность/проигрышность зависят только от состояния игры. От того, какой именно из двух игроков ходит, не зависит ничего: т.е. игроки полностью равноправны.

Кроме того, предполагается, что игроки располагают всей информацией (о правилах игры, возможных ходах, положении соперника).

Предполагается, что игра конечна, т.е. при любой стратегии игроки рано или поздно придут в проигрышную позицию, из которой нет переходов в другие позиции. Эта позиция является проигрышной для

игрока, который должен делать ход из этой позиции. Соответственно, она является выигрышной для игрока, пришедшего в эту позицию. Понятно, ничейных исходов в такой игре не бывает.

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

вершин не имеют исходящих рёбер, они является проигрышными вершинами (для игрока, который должен совершать ход из такой вершины).

Поскольку ничейных исходов не бывает, то все состояния игры распадаются на два класса: выигрышные и проигрышные. Выигрышные — это такие состояния, что найдётся ход текущего игрока, который приведёт

к неминуемому поражению другого игрока даже при его оптимальной игре. Соответственно, проигрышные состояния — это состояния, из которых все переходы ведут в состояния, приводящие к победе второго игрока, несмотря на "сопротивление" первого игрока. Иными словами, выигрышным будет состояние, из которого есть хотя бы один переход в проигрышное состояние, а проигрышным является состояние, из которого все переходы ведут в выигрышные состояния (или из которого вообще нет переходов).

Наша задача — для любой заданной игры провести классификацию состояний этой игры, т.е. для каждого состояния определить, выигрышное оно или проигрышное.

Теорию таких игр независимо разработали Роланд Шпраг (Roland Sprague) в 1935 г. и Патрик Майкл Гранди (Patrick Michael Grundy) в 1939 г.

Игра "Ним"

Эта игра является одним из примеров описываемых выше игр. Более того, как мы увидим чуть позже, любая из равноправных игр двух игроков на самом деле эквивалентна игре "ним" (англ. "nim"), поэтому изучение этой игры автоматически позволит нам решать все остальные игры (впрочем, об этом позже).

Исторически, эта игра была популярна ещё в древние времена. Вероятно, игра берёт своё происхождение в Китае — по крайней мере, китайская игра "Jianshizi" очень похожа на ним. В Европе первые упоминания о ниме относятся к XVI в. Само название "ним" придумал математик Чарлз Бутон (Charles Bouton), который в 1901 г. опубликовал полный анализ этой игры. Происхождение названия "ним" доподлинно неизвестно.

Описание игры

Игра "ним" представляет из себя следующую игру.

Есть несколько кучек, в каждой из которых по нескольку камней. За один ход игрок может взять из какой-нибудь одной кучки любое ненулевое число камней и выбросить их. Соответственно, проигрыш наступает, когда ходов больше не осталось, т.е. все кучки пусты.

Итак, состояние игры "ним" однозначно описывается неупорядоченным набором натуральных чисел. За один ход разрешается строго уменьшить любое из чисел (если в результате число станет нулём, то оно удаляется из набора).

Решение нима

Решение этой игры опубликовал в 1901 г. Чарлз Бутон (Charles L. Bouton), и выглядит оно следующим образом.

Теорема. Текущий игрок имеет выигрышную стратегию тогда и только тогда, когда XOR-сумма размеров кучек отлична от нуля. В противном случае текущий игрок находится в проигрышном состоянии. (XOR-суммой чисел

называется выражение , где — операция побитового исключающего или)

Доказательство.

Основная суть приведённого ниже доказательства — в наличии симметричной стратегии

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

Приступим теперь к формальному доказательству (оно будет конструктивным, т.е. мы покажем, как именно выглядит симметричная стратегия противника — какой именно ход нужно будет ему совершать).

Доказывать теорему будем по индукции.

Для пустого нима (когда размеры всех кучек равны нулю) XOR-сумма равна нулю, и теорема верна.

Пусть теперь мы хотим доказать теорему для некоторого состояния игры, из которого есть хотя бы один переход. Пользуясь предположением индукции (и ацикличностью игры) мы считаем, что теорема доказана для всех состояний, в которые мы можем попасть из текущего.

Тогда доказательство распадается на две части: если XOR-сумма в текущем состоянии , то надо доказать, что текущее состояние проигрышно, т.е. все переходы из него ведут в состояния с XOR-суммой . Если же

, то надо доказать, что найдётся переход, приводящий нас в состояние с .

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

 

 

 

Но поскольку

, то это означает, что

. Значит, новое состояние будет иметь ненулевую XOR-сумму, т.

е., согласно базе индукции, будет выигрышным, что и требовалось доказать.

Пусть . Тогда наша задача — доказать, что текущее состояние — выигрышно, т.е. из него существует ход в проигрышное состояние (с нулевой XOR-суммой).

Рассмотрим битовую запись числа . Возьмём старший ненулевой бит, пусть его номер равен . Пусть — номер той кучки, у размер которой -ый бит отличен от нуля (такое найдётся, иначе бы в XOR-сумме этот бит

не получился бы отличным от нуля).

Тогда, утверждается, искомый ход — это изменить -ую кучку, сделав её размера . Убедимся в этом.

Сначала надо проверить, что это ход корректный, т.е. что

. Однако это верно, поскольку все биты, старшие

-го, у

и

совпадают, а в -ом бите у

будет ноль, а у

будет единица.

Теперь посчитаем, какая XOR-сумма получится при этом ходе:

Таким образом, указанный нами ход — действительно ход в проигрышное состояние, а это и доказывает, что текущее состояние выигрышно.

Теорема доказана.

Следствие. Любое состояние ним-игры можно заменить эквивалентным состоянием, состоящим из единственной кучки размера, равного XOR-сумме размеров кучек в старом состоянии.

Иными словами, при анализе нима с несколькими кучками можно посчитать XOR-сумму их размеров, и перейти к анализу нима из единственной кучки размера — как показывает только что доказанная теорема, выигрышность/проигрышность от этого не изменится.

Эквивалентность любой игры ниму. Теорема Шпрага-Гранди

Здесь мы покажем, как любой игре (равноправной игре двух игроков) поставить в соответствие ним. Иными словами, любому состоянию любой игры мы научимся ставить в соответствие ним-кучку, полностью описывающее состояние исходной игры.

Лемма о ниме с увеличениями

Докажем сначала очень важную лемму — лемму о ниме с увеличениями.

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

соответствии с некими правилами, см. следующий абзац).

Здесь важно понимать, что правила того, как именно игрок может совершать увеличения, нас не интересуют

— однако такие правила всё же должны быть, чтобы наша игра по-прежнему была ациклична. Ниже в разделе "Примеры игр" рассмотрены примеры таких игр: "лестничный ним", "nimble-2", "turning turtles".

Повторимся, лемма будет доказана нами вообще для любых игр такого вида — игр вида "ним с увеличениями"; конкретные правила увеличений в доказательстве никак не используются.

Формулировка леммы. Ним с увеличениями эквивалентен обычному ниму, в том смысле, что выигрышность/проигрышность состояния определяется по теореме Бутона согласно XOR-сумме размеров кучек. (Или, иными словами, суть леммы в том, что увеличения бесполезны, их нет смысла применять в оптимальной стратегии, и они никак не меняют выигрышность/проигрышность по сравнению с обычным нимом.)

Доказательство.

Идея доказательства, как и в теореме Бутона — в наличии симметричной стратегии. Мы покажем, что увеличения ничего не меняют, поскольку после того, как один из игроков прибегнет к увеличению, другой сможет симметрично ответить ему.

В самом деле, предположим, что текущий игрок совершает ход-увеличение какой-либо кучки. Тогда его соперник сможет просто ответить ему, уменьшив обратно эту кучку до старого значения — ведь обычные ходы нима у нас по-прежнему могут свободно использоваться.

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

Теорема Шпрага-Гранди об эквивалентности любой игры ниму

Перейдём теперь к самому главному в данной статье факту — теореме об эквивалентности ниму любой равноправной игры двух игроков.

Теорема Шпрага-Гранди. Рассмотрим любое состояние некоторой равноправной игры двух игроков. Пусть из него есть переходы в некоторые состояния (где ). Утверждается, что состоянию этой

игры можно поставить в соответствие кучку нима некоторого размера (которая будет полностью описывать состояние

нашей игры — т.е. эти два состояния двух разных игр будут эквивалентны). Это число — называется

значением Шпрага-Гранди состояния .

Более того, это число можно находить следующим рекурсивным образом: посчитаем значение Шпрага-Гранди по каждому переходу , и тогда выполняется:

где функция от множества чисел возвращает наименьшее неотрицательное число, не встречающееся в этом множестве (название "mex" — это сокращение от "minimum excludant").

Таким образом, мы можем, стартуя от вершин без исходящих рёбер, постепенно посчитать значения Шпрага-Гранди для всех состояний нашей игры. Если значение Шпрага-Гранди какого-

либо состояния равно нулю, то это состояние проигрышно, иначе — выигрышно. Доказательство. Доказывать теорему будем по индукции.

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

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

Посчитаем величину

. Тогда, согласно определению функции

, мы получаем, что

для любого числа в промежутке

найдётся хотя бы один соответствующий переход в какое-то из -ых

состояние. Кроме того, могут существовать также дополнительные переходы — в состояния со значениями Гранди, большими .

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

Следовательно, величина

действительно является искомым значением Шпрага-Гранди

для текущего состояния, что и требовалось доказать.

Применение теоремы Шпрага-Гранди

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]