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

e-maxx algorithm original: C/C++ #algorithm #emaxx #game-theory #graph
题目文本会按所选界面语言从俄语翻译;代码保持不变。

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

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

Описание 算法а

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

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

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

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

под второе правило, то все эти вершины - ничейные. Таким образом, уже ясен 算法, работающий за асимптотику 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 посещается нашим 算法ом ровно один раз, а ничейные вершины и вовсе не посещаются. Следовательно, Asymptotic complexity действительно O (M).

实现

Рассмотрим реализацию поиска в глубину, в предположении, что 图 игры построен в памяти, степени исхода посчитаны и записаны в 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);

} }

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

Чтобы 算法 стал более ясным, рассмотрим его на конкретном 示例е. 题意 задачи. Имеется поле размером MxN клеток, в некоторые клетки заходить нельзя. Известны начальные координаты полицейского и вора. Также на карте может присутствовать 输出. Если полицейский окажется в одной клетке с вором, то выиграл полицейский. Если же вор окажется в клетке с 输出ом (и в этой клетке не стоит полицейский), то выиграет вор. Полицейский может ходить в 8 направлениях, вор - только в 4 (вдоль осей координат). И полицейский, и вор могут пропустить свой ход. Первым ход делает полицейский. Построение 图а. Построим 图 игры. Мы должны формализовать правила игры. Текущее состояние игры определяется координатами полицейского P, вора T, а также булева переменная Pstep, которая определяет, кто будет делать следующий ход. Следовательно, vertex 图а определена тройкой (P,T,Pstep). 图 построить легко, просто соответствуя условию. Далее нужно определить, какие вершины являются выигрышными или проигрышными изначально. Здесь есть тонкий момент. Выигрышность/проигрышность вершины помимо координат зависит и от Pstep - чей сейчас ход. Если сейчас ход полицейского, то vertex выигрышная, если координаты полицейского и вора совпадают; vertex проигрышная, если она не выигрышная и вор находится на 输出е. Если же сейчас ход вора, то vertex выигрышная, если вор находится на 输出е, и проигрышная, если она не выигрышная и координаты полицейского и вора совпадают. Единственный момент, которой нужно решить - строить 图 явно или делать это "на ходу", прямо в поиске в глубину. С одной стороны, если строить 图 предварительно, то будет меньше вероятность ошибиться. С другой стороны, это увеличит объём кода, да и 运行时间 будет в несколько раз медленнее, чем если строить 图 "на ходу". 实现 всей программы:

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# 解法

自动草稿,提交前请检查
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++ 解法

匹配/原始
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 解法

自动草稿,提交前请检查
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");
    }
}

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

Vacancies for this task

活跃职位 with overlapping task tags are 已显示.

所有职位
目前还没有活跃职位。