【LeetCode 913】cat-and-mouse | BFS+记忆化 | 极大极小搜索+AB剪枝 | H

LeetCode 913. Cat and Mouse

核心: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:

  • 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:

  1. 3 <= graph.length <= 50
  2. It is guaranteed that graph[1] is non-empty.
  3. It is guaranteed that graph[2] contains a non-zero element. 

 

Analysis  &  AC code:

 

一、题目大意

  1. 输入一个无向图代表迷宫,然后迷宫里面有一只猫和一只老鼠
  2. 老鼠的初始位置是顶点1,猫的初始位置是顶点2
  3. 猫和老鼠轮流沿着图的边移动(老鼠先移动)
  4. 猫移动到老鼠所在的格子则猫获胜;老鼠移动到顶点0则老鼠胜
  5. 假设双方都足够聪明,问最终是猫获胜还是老鼠获胜还是陷入僵局

 

二、思路产生

要解决这道题,首先要理解“足够聪明”这个词。也就是说老鼠和猫都有能力“穷举”所有未来的情况,然后选择对自己最好的决策。这给了我们一个思路提示,也就是:

我们应该从最终的胜利局面为基础,一步步倒推,得到某些中间过程也是必胜局面,然后再以这些局面倒推,再产生其他的必胜局面,一直重复这个过程直到到无法再推出新的局面为止。至此,所有已经被推到的局面的集合就成为了猫或老鼠的必胜局面集合,而未被推到的局面集合(也就是必胜局面集合对于全集的补集)就是僵持局面集合。我们只要看我们输入的局面属于哪个集合,就可以知道这个局面接下来会“足够聪明地”发展成为老鼠胜、猫胜还是僵持。

这个“不断倒推/扩展”其实也就是BFS。然后为了表示所有局面(一个局面由老鼠位置、猫位置、当前轮到老鼠移动还是猫移动这三个量唯一确定),我们还需要开一个三维数组分别表示这三个量。这样也就可以进行记忆化搜索

 

 

三、初始化和搜索规则

  • ①【初始化】
    • 把老鼠在0且猫不在0的所有局面标记为老鼠的必胜局面;把猫鼠都在非0点的所有局面标记为猫的必胜局面
    • 把其他局面标记为僵持局面(可以默认用0表示)
    • 标记必胜局面的同时把它们入队
    • 计算一下由局面扩展关系构成的图的每个顶点(代表一个局面)的度(代表有多少个父局面可以扩展到本局面),在之后的BFS更新过程中会用到度。
  • ②【BFS队列中存放的局面】
    • 始终存放着猫或鼠的必胜局面
  • ③【如何扩展子局面】
    • 根据当前行动者是猫/鼠,扩展到当前猫/鼠所在位置的所有邻接点,同时调换当前行动者,成为子局面
  • ④【何时停止搜索】
    • 队列中最后一个局面出队,然后扩展子局面,发现其扩展出的子局面均已经访问过,说明此时已经找寻了所有可能达到的必胜局面,可以停止搜索了。

 

 

四、关键搜索过程

​​​出队一个必胜的父局面,然后扩展其子局面。遍历子局面,对于一个不是必胜局面的子局面:

  • 如果子局面当前行动者是
    • 如果父局面是猫的必胜局面,那么这个子局面也是的必胜局面。标记历史表,然后把这个子局面入队。
    • 如果父局面是鼠的必胜局面,那么让子局面的度自减。自减之后如果度变成了0,说明所有可能扩展到此子局面的父局面都是鼠的必胜局面,由此可以推导出此子局面必定是猫的必败局面,也就是鼠的必胜局面。标记历史表,然后把这个局面入队(这个局面也是必胜局面,是鼠的必胜局面)。
  • 如果子局面当前行动者是
    • 如果父局面是鼠的必胜局面,那么这个子局面也是的必胜局面。标记历史表,然后把这个子局面入队。
    • 如果父局面是的必胜局面,那么让子局面的度自减。自减之后如果度变成了0,说明所有可能扩展到此子局面的父局面都是的必胜局面,由此可以推导出此子局面必定是鼠的必败局面,也就是猫的必胜局面。标记历史表,然后把这个局面入队(这个局面也是必胜局面,是猫鼠的必胜局面)。

 

按照上述搜索方式进行搜索,最后查记忆表即可得到答案。

 

这是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<vector<int>> edge;

		void initSearch(void)
		{
			/* 初始化局面构成的图的度,注意这里的图不是猫鼠的路径图,是局面之间扩展关系形成的图。
			 * 图的顶点是一个局面
			 * 顶点的度表示有多少个父局面可以扩展到本局面 
			 */
			for (int mouse_pos=0; mouse_pos<V; ++mouse_pos)
			{
				for (int cat_pos=0; cat_pos<V; ++cat_pos)
				{
					degree[mouse_pos][cat_pos][MOUSE] = edge[mouse_pos].size();
					degree[mouse_pos][cat_pos][CAT] = edge[cat_pos].size();
					for (int dest : edge[cat_pos])
					{
						if (dest == 0)
						{
							degree[mouse_pos][cat_pos][CAT]--;
						}
					}
				}
			}

			head = tail = 0;
			for (int v=0; v<V; ++v)
			{
				for (int now_player = MOUSE; now_player <= CAT; ++now_player)
				{
					winner[0][v][now_player] = MOUSE;
					queue[tail++] = State(0, v, now_player, MOUSE);
					if (v != 0)
					{
						winner[v][v][now_player] = CAT;
						queue[tail++] = State(v, v, now_player, CAT);
					}
				}
			}
		}

		int memSearch(void)
		{
			while (head != tail)
			{
				const State &now_state = queue[head++];

				Choice ava_choices[233];
				int ava_cnt = generPrevChoices(ava_choices, now_state.mouse_pos, now_state.cat_pos, now_state.now_player);
				for (Choice *it=ava_choices, *E=it+ava_cnt; it!=E; ++it)
				{
					int prev_mouse_pos = it->mouse_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<vector<int>>& 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上再写一次了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值