核心:BFS + 记忆化(标程) / 极大极小搜索 + alpha-beta剪枝(近似解)
URL:【LeetCode 913】cat-and-mouse
Difficulty : Hard Discuss (32)
ACCEPTED 1,341 SUBMISSIONS 6,005
Description :
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:
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:
Analysis & AC code:
一、题目大意
二、思路产生
要解决这道题,首先要理解“足够聪明”这个词。也就是说老鼠和猫都有能力“穷举”所有未来的情况,然后选择对自己最好的决策。这给了我们一个思路提示,也就是:
我们应该从最终的胜利局面为基础,一步步倒推,得到某些中间过程也是必胜局面,然后再以这些局面倒推,再产生其他的必胜局面,一直重复这个过程直到到无法再推出新的局面为止。至此,所有已经被推到的局面的集合就成为了猫或老鼠的必胜局面集合,而未被推到的局面集合(也就是必胜局面集合对于全集的补集)就是僵持局面集合。我们只要看我们输入的局面属于哪个集合,就可以知道这个局面接下来会“足够聪明地”发展成为老鼠胜、猫胜还是僵持。
这个“不断倒推/扩展”其实也就是BFS。然后为了表示所有局面(一个局面由老鼠位置、猫位置、当前轮到老鼠移动还是猫移动这三个量唯一确定),我们还需要开一个三维数组分别表示这三个量。这样也就可以进行记忆化搜索。
三、初始化和搜索规则
四、关键搜索过程
出队一个必胜的父局面,然后扩展其子局面。遍历子局面,对于一个不是必胜局面的子局面:
按照上述搜索方式进行搜索,最后查记忆表即可得到答案。
这是AC代码:
class Solution
{
public:
enum PLAYER_FLAG
{
NO_PLAYER,
MOUSE,
CAT,
PLAYER_CNT
};
struct Choice
{
int mouse_pos;
int cat_pos;
int now_player;
Choice(void) { }
Choice(int mouse_pos, int cat_pos, int now_player) :
mouse_pos(mouse_pos), cat_pos(cat_pos), now_player(now_player) { }
};
struct State
{
int mouse_pos;
int cat_pos;
int now_player;
int win_player;
State(void) { }
State(int mouse_pos, int cat_pos, int now_player, int win_player) :
mouse_pos(mouse_pos), cat_pos(cat_pos),
now_player(now_player), win_player(win_player) { }
};
int V;
char winner[55][55][PLAYER_CNT]; // history
char degree[55][55][PLAYER_CNT];
State queue[12345];
int head, tail;
vector> edge;
void initSearch(void)
{
/* 初始化局面构成的图的度,注意这里的图不是猫鼠的路径图,是局面之间扩展关系形成的图。
* 图的顶点是一个局面
* 顶点的度表示有多少个父局面可以扩展到本局面
*/
for (int mouse_pos=0; mouse_posmouse_pos;
int prev_cat_pos = it->cat_pos;
int prev_player = it->now_player;
if (winner[prev_mouse_pos][prev_cat_pos][prev_player] == NO_PLAYER)
{
if (prev_player == now_state.win_player) // 存在父局面是必胜局面,prev_player必胜
{
winner[prev_mouse_pos][prev_cat_pos][prev_player] = now_state.win_player;
queue[tail++] = State(prev_mouse_pos, prev_cat_pos, prev_player, now_state.win_player);
}
else
{
degree[prev_mouse_pos][prev_cat_pos][prev_player]--;
if (!degree[prev_mouse_pos][prev_cat_pos][prev_player]) // 所有可能的父局面全部都是必败局面,连拖延都没法拖延,prev_player必败
{
winner[prev_mouse_pos][prev_cat_pos][prev_player] = PLAYER_CNT - prev_player;
queue[tail++] = State(prev_mouse_pos, prev_cat_pos, prev_player, PLAYER_CNT - prev_player);
}
}
}
}
}
return winner[1][2][MOUSE];
}
int generPrevChoices(Choice *avaChoices, int mouse_pos, int cat_pos, int now_player)
{
int cnt = 0;
if (now_player == CAT)
{
for (auto prev_mouse_pos : edge[mouse_pos])
avaChoices[cnt++] = Choice(prev_mouse_pos, cat_pos, MOUSE);
}
else
{
for (auto prev_cat_pos : edge[cat_pos])
if (prev_cat_pos)
avaChoices[cnt++] = Choice(mouse_pos, prev_cat_pos, CAT);
}
return cnt;
}
int catMouseGame(const vector>& graph)
{
memset(winner, 0, sizeof(winner));
memset(degree, 0, sizeof(degree));
memset(queue, 0, sizeof(queue));
head = tail = 0;
this->V = graph.size();
this->edge = graph;
initSearch();
return memSearch();
}
};
不过这道题还有一种近似求法(利用极大极小搜索+AB剪枝),这个具体代码可以看BHOJ这道题,这种解法我就..懒得再在LeetCode上再写一次了。