|
| 1 | +<!--?title Games on arbitrary graphs --> |
| 2 | +# Games on arbitrary graphs |
| 3 | + |
| 4 | +Let a game be played by two players on an arbitrary graph $G$. |
| 5 | +I.e. the current state of the game is a certain vertex. |
| 6 | +The player perform moves by turns, and move from the current vertex to an adjacent vertex using a connecting edge. |
| 7 | +Depending on the game, the person that is unable to move will either lose or win the game. |
| 8 | + |
| 9 | +We consider the most general case, the case of an arbitrary directed graph with cycles. |
| 10 | +It is our task to determine, given an initial state, who will win the game if both players play with optimal strategies or determine that the result of the game will be a draw. |
| 11 | + |
| 12 | +We will solve this problem very efficiently. |
| 13 | +We will find the solution for all possible starting vertices of the graph in linear time with respect to the number of edges: $O(m)$. |
| 14 | + |
| 15 | +## Description of the algorithm |
| 16 | + |
| 17 | +We will call a vertex a winning vertex, if the player starting at this state will win the game, if he plays optimal (regardless what turns the other player makes). |
| 18 | +And similarly we will call a vertex a losing vertex, if the player starting at this vertex will lose the game, if the opponent plays optimal. |
| 19 | + |
| 20 | +For some of the vertices of the graph we already know in advance that they are winning or losing vertices: namely all vertices that have no outgoing edges. |
| 21 | + |
| 22 | +Also we have the following **rules**: |
| 23 | + |
| 24 | +- if a vertex has an outgoing edge that leads to a losing vertex, then the vertex itself is a winning vertex. |
| 25 | +- if all outgoing edges of a certain vertex lead to winning vertices, then the vertex itself is a losing vertex. |
| 26 | +- if at some point there are still undefined vertices, and neither will fit the first or the second rule, then each of these vertices, when used as a starting vertex, will lead to a draw if both player play optimal. |
| 27 | + |
| 28 | +Thus we can define an algorithm which runs in $O(n m)$ time immediately. |
| 29 | +We go through all vertices and try to apply the first or second rule, and repeat. |
| 30 | + |
| 31 | +However we can accelerate this procedure, and get the complexity down to $O(m)$. |
| 32 | + |
| 33 | +We will go over all the vertices, for which we initially know if they are winning or losing states. |
| 34 | +For each of them we start a [depth first search](./graph/depth-first-search.html). |
| 35 | +This DFS will move back over the reversed edges. |
| 36 | +First of all, it will not enter vertices which already are defined as winning or losing vertices. |
| 37 | +And further, if the search goes from a losing vertex to an undefined vertex, then we mark this one as a winning vertex, and continue the DFS using this new vertex. |
| 38 | +If we go from a winning vertex to an undefined vertex, then we must check whether all edges from this one leads to winning vertices. |
| 39 | +We can perform this test in $O(1)$ by storing the number of edges that lead to a winning vertex for each vertex. |
| 40 | +So if we go from a winning vertex to an undefined one, then we increase the counter, and check if this number is equal to the number of outgoing edges. |
| 41 | +If this is the case, we can mark this vertex as a losing vertex, and continue the DFS from this vertex. |
| 42 | +Otherwise we don't know yet, if this vertex is a winning or losing vertex, and therefore it doesn't make sense to keep continuing the DFS using it. |
| 43 | + |
| 44 | +In total we visit every winning and every losing vertex exactly once (undefined vertices are not visited), and we go over each edge also at most one time. |
| 45 | +Hence the complexity is $O(m)$. |
| 46 | + |
| 47 | +## Implementation |
| 48 | + |
| 49 | +Here is the implementation of such a DFS. |
| 50 | +We assume that the variable `adj_rev` stores the adjacency list for the graph in **reversed** form, i.e. instead of storing the edge $(i, j)$ of the graph, we store $(j, i)$. |
| 51 | +Also for each vertex we assume that the outgoing degree is already computed. |
| 52 | + |
| 53 | +```cpp |
| 54 | +vector<vector<int>> adj_rev; |
| 55 | + |
| 56 | +vector<bool> winning; |
| 57 | +vector<bool> losing; |
| 58 | +vector<bool> visited; |
| 59 | +vector<int> degree; |
| 60 | + |
| 61 | +void dfs(int v) { |
| 62 | + visited[v] = true; |
| 63 | + for (int u : adj_rev[v]) { |
| 64 | + if (!visited[v]) { |
| 65 | + if (losing[v]) |
| 66 | + winning[u] = true; |
| 67 | + else if (--degree[u] == 0) |
| 68 | + losing[u] = true; |
| 69 | + else |
| 70 | + continue; |
| 71 | + dfs(u); |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | +``` |
| 76 | +
|
| 77 | +## Example: "Policeman and thief" |
| 78 | +
|
| 79 | +Here is a concrete example of such a game. |
| 80 | +
|
| 81 | +There is $m \times n$ board. |
| 82 | +Some of the cells cannot be entered. |
| 83 | +The initial coordinates of the police officer and of the thief are known. |
| 84 | +One of the cells is the exit. |
| 85 | +If the policeman and the thief are located at the same cell at any moment, the policeman wins. |
| 86 | +If the thief is at the exit cell (without the policeman also being on the cell), then the thief wins. |
| 87 | +The policeman can walk in all 8 directions, the thief only in 4 (along the coordinate axis). |
| 88 | +Both the policeman and the thief will take turns moving. |
| 89 | +However they also can skip a turn if they want to. |
| 90 | +The first move is made by the policeman. |
| 91 | +
|
| 92 | +We will now **construct the graph**. |
| 93 | +For this we must formalize the rules of the game. |
| 94 | +The current state of the game is determined by the coordinates of the police offices $P$, the coordinates of the thief $T$, and also by whose turn it is, let's call this variable $P_{\text{turn}}$ (which is true when it is the policeman's turn). |
| 95 | +Therefore a vertex of the graph is determined by the triple $(P, T, P_{\text{turn}})$ |
| 96 | +The graph then can be easily constructed, simply by following the rules of the game. |
| 97 | +
|
| 98 | +Next we need to determine which vertices are winning and which are losing ones initially. |
| 99 | +There is a **subtle point** here. |
| 100 | +The winning / losing vertices depend, in addition to the coordinates, also on $P_{\text{turn}}$ - whose turn it. |
| 101 | +If it is the policeman's turn, then the vertex is a winning vertex, if the coordinates of the policeman and the thief coincide, and the vertex is a losing one if it is not a winning one and the thief is on the exit vertex. |
| 102 | +If it is the thief's turn, then a vertex is a losing vertex, if the coordinates of the two players coincide, and it is a winning vertex if it is not a losing one, and the thief is at the exit vertex. |
| 103 | +
|
| 104 | +The only point before implementing is not, that you need to decide if you want to build the graph **explicitly** or just construct it **on the fly**. |
| 105 | +On one hand, building the graph explicitly will be a lot easier and there is less chance of making mistakes. |
| 106 | +On the other hand, it will increase the amount of code and the running time will be slower than if you build the graph on the fly. |
| 107 | +
|
| 108 | +The following implementation will construct the graph explicitly: |
| 109 | +
|
| 110 | +```cpp |
| 111 | +struct State { |
| 112 | + int P, T; |
| 113 | + bool Pstep; |
| 114 | +}; |
| 115 | +
|
| 116 | +vector<State> adj_rev[100][100][2]; // [P][T][Pstep] |
| 117 | +bool winning[100][100][2]; |
| 118 | +bool losing[100][100][2]; |
| 119 | +bool visited[100][100][2]; |
| 120 | +int degree[100][100][2]; |
| 121 | +
|
| 122 | +void dfs(State v) { |
| 123 | + visited[v.P][v.T][v.Pstep] = true; |
| 124 | + for (State u : adj_rev[v.P][v.T][v.Pstep]) { |
| 125 | + if (!visited[u.P][u.T][u.Pstep]) { |
| 126 | + if (losing[v.P][v.T][v.Pstep]) |
| 127 | + winning[u.P][u.T][u.Pstep] = true; |
| 128 | + else if (--degree[u.P][u.T][u.Pstep] == 0) |
| 129 | + losing[u.P][u.T][u.Pstep] = true; |
| 130 | + else |
| 131 | + continue; |
| 132 | + dfs(u); |
| 133 | + } |
| 134 | + } |
| 135 | +} |
| 136 | +
|
| 137 | +int main() { |
| 138 | + int n, m; |
| 139 | + cin >> n >> m; |
| 140 | + vector<string> a(n); |
| 141 | + for (int i = 0; i < n; i++) |
| 142 | + cin >> a[i]; |
| 143 | +
|
| 144 | + for (int P = 0; P < n*m; P++) { |
| 145 | + for (int T = 0; T < n*m; T++) { |
| 146 | + for (int Pstep = 0; Pstep <= 1; Pstep++) { |
| 147 | + int Px = P/m, Py = P%m, Tx = T/m, Ty = T%m; |
| 148 | + if (a[Px][Py]=='*' || a[Tx][Ty]=='*') |
| 149 | + continue; |
| 150 | + |
| 151 | + bool& win = winning[P][T][Pstep]; |
| 152 | + bool& lose = losing[P][T][Pstep]; |
| 153 | + if (Pstep) { |
| 154 | + win = Px==Tx && Py==Ty; |
| 155 | + lose = !win && a[Tx][Ty] == 'E'; |
| 156 | + } else { |
| 157 | + lose = Px==Tx && Py==Ty; |
| 158 | + win = !lose && a[Tx][Ty] == 'E'; |
| 159 | + } |
| 160 | + if (win || lose) |
| 161 | + continue; |
| 162 | +
|
| 163 | + State st = {P,T,!Pstep}; |
| 164 | + adj_rev[P][T][Pstep].push_back(st); |
| 165 | + st.Pstep = Pstep; |
| 166 | + degree[P][T][Pstep]++; |
| 167 | + |
| 168 | + const int dx[] = {-1, 0, 1, 0, -1, -1, 1, 1}; |
| 169 | + const int dy[] = {0, 1, 0, -1, -1, 1, -1, 1}; |
| 170 | + for (int d = 0; d < (Pstep ? 8 : 4); d++) { |
| 171 | + int PPx = Px, PPy = Py, TTx = Tx, TTy = Ty; |
| 172 | + if (Pstep) { |
| 173 | + PPx += dx[d]; |
| 174 | + PPy += dy[d]; |
| 175 | + } else { |
| 176 | + TTx += dx[d]; |
| 177 | + TTy += dy[d]; |
| 178 | + } |
| 179 | +
|
| 180 | + if (PPx >= 0 && PPx < n && PPy >= 0 && PPy < m && a[PPx][PPy] != '*' && |
| 181 | + TTx >= 0 && TTx < n && TTy >= 0 && TTy < m && a[TTx][TTy] != '*') |
| 182 | + { |
| 183 | + adj_rev[PPx*m+PPy][TTx*m+TTy][!Pstep].push_back(st); |
| 184 | + ++degree[P][T][Pstep]; |
| 185 | + } |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | +
|
| 191 | + for (int P = 0; P < n*m; P++) { |
| 192 | + for (int T = 0; T < n*m; T++) { |
| 193 | + for (int Pstep = 0; Pstep <= 1; Pstep++) { |
| 194 | + if ((winning[P][T][Pstep] || losing[P][T][Pstep]) && !visited[P][T][Pstep]) |
| 195 | + dfs({P, T, (bool)Pstep}); |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | +
|
| 200 | + int P_st, T_st; |
| 201 | + for (int i = 0; i < n; i++) { |
| 202 | + for (int j = 0; j < m; j++) { |
| 203 | + if (a[i][j] == 'P') |
| 204 | + P_st = i*m+j; |
| 205 | + else if (a[i][j] == 'T') |
| 206 | + T_st = i*m+j; |
| 207 | + } |
| 208 | + } |
| 209 | +
|
| 210 | + if (winning[P_st][T_st][true]) { |
| 211 | + cout << "Police catches the thief" << endl; |
| 212 | + } else if (losing[P_st][T_st][true]) { |
| 213 | + cout << "The thief escapes" << endl; |
| 214 | + } else { |
| 215 | + cout << "Draw" << endl; |
| 216 | + } |
| 217 | +} |
| 218 | +``` |
0 commit comments