E139. Игры на произвольных grafoах

e-maxx algorithm original: C/C++ #algorithm #emaxx #game-theory #graph
O texto da tarefa é traduzido do russo para o idioma selecionado. O código permanece sem alterações.

Источник: e-maxx.ru/algo, страница PDF 455.

Пусть игра ведётся двумя игроками на некотором grafoе G. Т.е. текущее состояние игры - это некоторая vertex grafoа, и из каждой вершины рёбра идут в те вершины, в которые можно пойти следующим ходом. Мы рассматриваем самый общий случай - случай произвольного ориентированного grafoа с циклами. it is required для заданной начальной позиции определить, кто выиграет при оптимальной игре обоих игроков (или определить, что результатом будет ничья). Мы решим эту задачу очень эффективно - найдём ответы для всех вершин grafoа за линейное относительно количества рёбер время - O (M).

Описание Algoritmoа

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

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

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

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

под второе правило, то все эти вершины - ничейные. Таким образом, уже ясен Algoritmo, работающий за асимптотику O (N M) - мы перебираем все вершины, пытаемся к каждой применить первое либо второе правило, и если мы произвели какие-то изменения, то повторяем всё заново. Однако этот процесс поиска и обновления можно значительно ускорить, доведя асимптотику до линейной. Переберём все вершины, про которые изначально известно, что они выигрышные или проигрышные. Из каждой из них пустим следующий Depth-first search. Этот Depth-first search будет двигаться по обратным рёбрам. Прежде всего, он не будет заходить в вершины, которые уже определены как выигрышные или проигрышные. Далее, если Depth-first search пытается пойти из проигрышной вершины в некоторую вершину, то её он помечает как выигрышную, и идёт в неё. Если же Depth-first search пытается пойти из выигрышной вершины в некоторую вершину, то он должен проверить, все ли рёбра ведут из этой вершины в выигрышные. Эту проверку легко осуществить за O (1), если в каждой вершине будем хранить счётчик рёбер, которые ведут в выигрышные вершины. Итак, если Depth-first search пытается пойти из выигрышной вершины в некоторую вершину, то он увеличивает в ней счётчик, и если счётчик сравнялся с количеством рёбер, исходящих из этой вершины, то эта vertex помечается как проигрышная, и Depth-first search идёт в эту вершину. Иначе же, если целевая vertex так и не определена как выигрышная или проигрышная, то Depth-first search в неё не заходит. Итого, мы получаем, что каждая выигрышная и каждая проигрышная vertex посещается нашим Algoritmoом ровно один раз, а ничейные вершины и вовсе не посещаются. Следовательно, Asymptotic complexity действительно O (M).

Implementação

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

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);

} }

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

Чтобы Algoritmo стал более ясным, рассмотрим его на конкретном Exemploе. Enunciado задачи. Имеется поле размером MxN клеток, в некоторые клетки заходить нельзя. Известны начальные координаты полицейского и вора. Также на карте может присутствовать Saída. Если полицейский окажется в одной клетке с вором, то выиграл полицейский. Если же вор окажется в клетке с Saídaом (и в этой клетке не стоит полицейский), то выиграет вор. Полицейский может ходить в 8 направлениях, вор - только в 4 (вдоль осей координат). И полицейский, и вор могут пропустить свой ход. Первым ход делает полицейский. Построение grafoа. Построим grafo игры. Мы должны формализовать правила игры. Текущее состояние игры определяется координатами полицейского P, вора T, а также булева переменная Pstep, которая определяет, кто будет делать следующий ход. Следовательно, vertex grafoа определена тройкой (P,T,Pstep). grafo построить легко, просто соответствуя условию. Далее нужно определить, какие вершины являются выигрышными или проигрышными изначально. Здесь есть тонкий момент. Выигрышность/проигрышность вершины помимо координат зависит и от Pstep - чей сейчас ход. Если сейчас ход полицейского, то vertex выигрышная, если координаты полицейского и вора совпадают; vertex проигрышная, если она не выигрышная и вор находится на Saídaе. Если же сейчас ход вора, то vertex выигрышная, если вор находится на Saídaе, и проигрышная, если она не выигрышная и координаты полицейского и вора совпадают. Единственный момент, которой нужно решить - строить grafo явно или делать это "на ходу", прямо в поиске в глубину. С одной стороны, если строить grafo предварительно, то будет меньше вероятность ошибиться. С другой стороны, это увеличит объём кода, да и Tempo de execução будет в несколько раз медленнее, чем если строить grafo "на ходу". Implementação всей программы:

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)

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

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

else

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

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

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;

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

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)

ppx += dx[d], ppy += dy[d];

else

ttx += dx[d], tty += dy[d];

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

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

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

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

{

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

pstep].push_back (st);

++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)
if ((win[p][t][pstep] || loose[p][t]

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

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')

p_st = i*m+j;

else if (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");

}

C# solução

rascunho automático, revisar antes de enviar
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    List<int> g [100];
    bool win [100];
    bool loose [100];
    bool[] used = new bool[100];
    int degree[100];
    void dfs (int v) {
            used[v] = true;
            for (List<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);
                    }
    }
    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 = new bool[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;
            List<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)
                                            wwin = px==tx && py==ty,   lloose
    = !wwin && a[tx][ty] == 'E';
                                    else
                                            wwin = a[tx][ty] == 'E',   lloose
    = !wwin && px==tx && py==ty;
                                    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;
                                    const int dx[] = { -1, 0, 1, 0,   -1, -1,
    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)
                                                    ppx += dx[d],  ppy += dy[d];
                                            else
                                                    ttx += dx[d],  tty += dy[d];
                                            if (ppx>=0 && ppx<n && ppy>=0 &&
    ppy<m && a[ppx][ppy]!='*' &&
                                                    ttx>=0 && ttx<n && tty>=0
    && tty<m && a[ttx][tty]!='*')
                                            {
                                                    g[ppx*m+ppy][ttx*m+tty][!
    pstep].push_back (st);
                                                    ++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)
                                    if ((win[p][t][pstep] || loose[p][t]
    [pstep]) && !used[p][t][pstep])
                                            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')
                                    p_st = i*m+j;
                            else if (a[i][j] == 'T')
                                    t_st = i*m+j;
            Console.WriteLine( (win[p_st][t_st][true] ? "WIN" : loose[p_st][t_st]
    [true] ? "LOSS" : "DRAW");
    }
}

C++ solução

correspondente/original
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);
                }
}
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)
                                        wwin = px==tx && py==ty,   lloose
= !wwin && a[tx][ty] == 'E';
                                else
                                        wwin = a[tx][ty] == 'E',   lloose
= !wwin && px==tx && py==ty;
                                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;
                                const int dx[] = { -1, 0, 1, 0,   -1, -1,
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)
                                                ppx += dx[d],  ppy += dy[d];
                                        else
                                                ttx += dx[d],  tty += dy[d];
                                        if (ppx>=0 && ppx<n && ppy>=0 &&
ppy<m && a[ppx][ppy]!='*' &&
                                                ttx>=0 && ttx<n && tty>=0
&& tty<m && a[ttx][tty]!='*')
                                        {
                                                g[ppx*m+ppy][ttx*m+tty][!
pstep].push_back (st);
                                                ++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)
                                if ((win[p][t][pstep] || loose[p][t]
[pstep]) && !used[p][t][pstep])
                                        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')
                                p_st = i*m+j;
                        else if (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");
}

Java solução

rascunho automático, revisar antes de enviar
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    ArrayList<Integer> g [100];
    boolean win [100];
    boolean loose [100];
    boolean used[100];
    int degree[100];
    void dfs (int v) {
            used[v] = true;
            for (ArrayList<Integer>::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);
                    }
    }
    struct state {
            char p, t;
            boolean pstep;
    };
    vector<state> g [100][100][2];
    // 1 = policeman coords; 2 = thief coords; 3 = 1 if policeman's step or 0
    if thief's.
    boolean win [100][100][2];
    boolean loose [100][100][2];
    boolean used[100][100][2];
    int degree[100][100][2];
    void dfs (char p, char t, boolean 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;
            ArrayList<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;
                                    boolean & wwin = win[p][t][pstep];
                                    boolean & lloose = loose[p][t][pstep];
                                    if (pstep)
                                            wwin = px==tx && py==ty,   lloose
    = !wwin && a[tx][ty] == 'E';
                                    else
                                            wwin = a[tx][ty] == 'E',   lloose
    = !wwin && px==tx && py==ty;
                                    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;
                                    const int dx[] = { -1, 0, 1, 0,   -1, -1,
    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)
                                                    ppx += dx[d],  ppy += dy[d];
                                            else
                                                    ttx += dx[d],  tty += dy[d];
                                            if (ppx>=0 && ppx<n && ppy>=0 &&
    ppy<m && a[ppx][ppy]!='*' &&
                                                    ttx>=0 && ttx<n && tty>=0
    && tty<m && a[ttx][tty]!='*')
                                            {
                                                    g[ppx*m+ppy][ttx*m+tty][!
    pstep].push_back (st);
                                                    ++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)
                                    if ((win[p][t][pstep] || loose[p][t]
    [pstep]) && !used[p][t][pstep])
                                            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')
                                    p_st = i*m+j;
                            else if (a[i][j] == 'T')
                                    t_st = i*m+j;
            System.out.println( (win[p_st][t_st][true] ? "WIN" : loose[p_st][t_st]
    [true] ? "LOSS" : "DRAW");
    }
}

Материал разбит как Algoritmoическая Tarefa: изучить постановку, понять асимптотику и реализовать Algoritmo на выбранном языке.

Vacancies for this task

vagas ativas with overlapping task tags are mostradas.

Todas as vagas
Ainda não há vagas ativas.