题目来源:https://leetcode.com/contest/weekly-contest-104/problems/cat-and-mouse/
问题描述
913. Cat and Mouse
A game on an undirected graph is played by two players, Mouse and Cat, who alternate turns.
The graph is given as follows: graph[a]
is a list of all nodes b
such that ab
is an edge of the graph.
Mouse starts at node 1 and goes first, Cat starts at node 2 and goes second, and there is a Hole at node 0.
During each player's turn, they must travel along one edge of the graph that meets where they are. For example, if the Mouse is at node 1
, it must travel to any node in graph[1]
.
Additionally, it is not allowed for the Cat to travel to the Hole (node 0.)
Then, the game can end in 3 ways:
- If ever the Cat occupies the same node as the Mouse, the Cat wins.
- If ever the Mouse reaches the Hole, the Mouse wins.
- If ever a position is repeated (ie. the players are in the same position as a previous turn, and it is the same player's turn to move), the game is a draw.
Given a graph
, and assuming both players play optimally, return 1
if the game is won by Mouse, 2
if the game is won by Cat, and 0
if the game is a draw.
Example 1:
Input: [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
Output: 0
Explanation:
4---3---1
| |
2---5
\ /
0
Note:
3 <= graph.length <= 50
- It is guaranteed that
graph[1]
is non-empty. - It is guaranteed that
graph[2]
contains a non-zero element.
------------------------------------------------------------
题意
老鼠和猫在无向图上的博弈,无向图上编号为0的点表示老鼠洞。博弈规则为奇数turn老鼠走,偶数turn猫走,每个turn, 老鼠/猫都必须走向一个相邻节点。此外猫不能走到老鼠洞上。博弈结束规则有3条:
(1) 老鼠走到老鼠洞则老鼠胜利;
(2) 猫抓到老鼠(猫和老鼠在图的同一个节点)则猫胜利;
(3) 若老鼠/猫走到自己之前走过的节点,则平局。
博弈开始前老鼠总是在节点1,猫总是在节点2.问给定无向图,老鼠/猫各自采取最优策略,求博弈结果(0表示平局,1表示老鼠胜利,2表示猫胜利)
这道题感觉给的数据有问题,或者是题目表述有问题。例如无向图G:
0
|
4 – 1 – 3 – 2
对应的输入为:[[3], [4], [3], [0, 1, 2], [1]]
老鼠turn 1的最优策略是走到4(走到3的话turn 2就被猫抓到了),猫turn 2的最优策略是走到3(猫只能往3走,别无选择),老鼠turn 3的最优策略是走回1(老鼠也只有这个选择),平局,答案是0。但是LeetCode官方给出的答案是2.
------------------------------------------------------------
思路
这种图上的博弈论,不像经典的博弈论问题如Bash Game, Fibonacci Game, Wythoff Game, Nim Game有O(1)的解法。这类图上的博弈论需要用搜索,思路非常类似CCF 201803-4 棋局评估(博弈论),对于博弈双方,每步搜索都向着有利于自己的方向。为了加快速度,采用记忆化搜索,即用mat数组保存搜索结果。
值得注意的是记忆化数组维度是N×N×T,其中N是图的节点数T是博弈进行的最长轮数,而不能简单地用N×N,因为要考虑到之前是否经过某些节点,因此状态实际是当前博弈双方所在的节点和之前(T-1)轮双方经过的节点。其实应该对“之前经过的节点”进行状态压缩,比如用二进制表示每个节点是否被经过。这里用轮数t做状态压缩,其实可能是一种错误的简化,不过鉴于题目数据有错,就这样吧不管他了。博弈的最大轮数是N, 因为老鼠最多经过N轮运动就能从图的任意节点走到节点0,如果博弈轮数超过N,则老鼠一定走重复了。
------------------------------------------------------------
代码
class Solution {
public:
vector<vector<int>> G;
int t = 0;
int n;
int mat[55][55][55]; // memory vector storing "dfs" result to speed up searching process
void init(vector<vector<int>> &graph)
{
G = graph;
n = G.size();
memset(mat, 0x3f, sizeof(mat));
}
int dfs(int x, int y, int t) // adversarial search, x: mouse position, y: cat position, t: turns
{
if (x == y)
{
return mat[x][y][t] = 2;
}
else if (x == 0)
{
return mat[x][y][t] = 1;
}
else if (t > n)
{
return 0;
}
else if (mat[x][y][t] != 0x3f3f3f3f)
{
return mat[x][y][t];
}
int i, len, v;
int cnt_lose, cnt_len;
vector<int> edge;
if (t % 2 == 0) // mouse's turn
{
edge = G.at(x);
len = edge.size();
cnt_lose = 0;
for (i = 0; i < len; i++)
{
v = dfs(edge.at(i), y, t + 1);
if (v == 1)
{
return mat[x][y][t] = 1;
}
else if (v == 2)
{
cnt_lose++;
}
}
if (cnt_lose == len)
{
return mat[x][y][t] = 2;
}
else
{
return mat[x][y][t] = 0;
}
}
else // cat's turn
{
edge = G.at(y);
len = edge.size();
cnt_lose = cnt_len = 0;
for (i = 0; i < len; i++)
{
if (edge.at(i) != 0) // cat cannot go into hole
{
cnt_len++;
v = dfs(x, edge.at(i), t + 1);
if (v == 2)
{
return mat[x][y][t] = 2;
}
else if (v == 1)
{
cnt_lose++;
}
}
}
if (cnt_lose == cnt_len)
{
return mat[x][y][t] = 1;
}
else
{
return mat[x][y][t] = 0;
}
}
}
int catMouseGame(vector<vector<int>>& graph) {
init(graph);
return dfs(1, 2, 0);
}
};