c++ 记忆化搜索_[力扣913] 染色BFS&记忆化搜索&minimax

0be4dc85e260765011e5e687dacc29fa.png

题目链接

913. 猫和老鼠

题目描述

两个玩家分别扮演猫(Cat)和老鼠(Mouse)在无向图上进行游戏,他们轮流行动。

该图按下述规则给出:graph[a] 是所有结点 b 的列表,使得 ab 是图的一条边。

老鼠从结点 1 开始并率先出发,猫从结点 2 开始且随后出发,在结点 0 处有一个洞。

在每个玩家的回合中,他们必须沿着与他们所在位置相吻合的图的一条边移动。例如,如果老鼠位于结点 1,那么它只能移动到 graph[1] 中的(任何)结点去。

此外,猫无法移动到洞(结点 0)里。

然后,游戏在出现以下三种情形之一时结束:

  1. 如果猫和老鼠占据相同的结点,猫获胜。
  2. 如果老鼠躲入洞里,老鼠获胜。
  3. 如果某一位置重复出现(即,玩家们的位置和移动顺序都与上一个回合相同),游戏平局。

给定 graph,并假设两个玩家都以最佳状态参与游戏,如果老鼠获胜,则返回 1;如果猫获胜,则返回 2;如果平局,则返回 0。

提示:

3 <= graph.length <= 200
保证 graph[1] 非空。
保证 graph[2] 包含非零元素。

样例

示例:
输入:[[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
输出:0
解释:
4---3---1
| |
2---5
/
0

算法1:染色BFS

状态表示:(m, c, t):

m : 老鼠位置
c : 猫的位置
t : 轮到谁动,t=0 老鼠动,t=1 猫动

状态的集合:有向图的节点;当前的可选方案(状态的可能的转移方向):有向边

每个节点有三种颜色:

  1. CAT=2: 猫赢
  2. MOUSE=1: 鼠赢
  3. DRAW=0: 胜负未知

minimax 的思想:老鼠尽量移动到染成 MOUSE 颜色的节点,其次是移动到 DRAW 颜色的节点,猫尽量移动到染成 CAT 颜色的节点,其次是 DRAW 点,最后才是 MOUSE 节点。

初始时有一些状态对应的节点胜负已定(已经确定颜色):m=0(鼠在洞里) 时鼠胜,m=c(鼠和猫同位置)时猫胜

状态转移方向: 对于每个节点 (m, c, t):

t=0 时(老鼠动)

  • 若存在一个子节点被标记为MOUSE颜色,则该点也标记为MOUSE颜色
  • 若所有的子节点都标记为CAT颜色,则改点也标记为CAT颜色

t=1时(猫动)

  • 若存在一个子节点被标记为CAT颜色,则该点也标记为CAT颜色
  • 若所有的子节点都标记为MOUSE颜色,则改点也标记为MOUSE颜色

BFS过程

将所有初始时胜负已知的节点入队

对于队中弹出的每个节点,考察其父节点:对满足条件的,立即染色,若不满足条件,尽量减少标为 DRAW 的子节点个数,直至标为 DRAW 的子节点个数为0,再染色。所有染色的点,在染色后立即进队。

队列里存放的内容:({m, c, t, color})

代码(c++)

class Solution_2 {
public:
    int catMouseGame(vector<vector<int> >& graph) {
        int N = graph.size();
        const int DRAW = 0, MOUSE = 1, CAT = 2;

        vector<vector<vector<int> > > color(N, vector<vector<int> >(N, vector<int>(3)));
        vector<vector<vector<int> > > draw_degree(N, vector<vector<int> >(N, vector<int>(3))); // 标记为 DRAW 的子节点个数

        // draw_degree[node] := 节点的子节点个数
        for(int m = 0; m < N; ++m)
            for(int c = 0; c < N; ++c)
            {
                draw_degree[m][c][1] = graph[m].size(); // t = 1, 轮鼠动
                draw_degree[m][c][2] = graph[c].size(); // t = 2, 轮猫动
                for(int x: graph[c])
                {
                    if(x == 0) // 猫无法移动到洞里
                    {
                        draw_degree[m][c][2]--;
                        break;
                    }
                }
            }

        // 将初始时胜负已知的点染色,并进队
        queue<vector<int> > q;
        for(int i = 0; i < N; ++i)
            for(int t = 1; t <= 2; ++t)
            {
                color[0][i][t] = MOUSE;
                q.push(vector<int>({0, i, t, MOUSE}));
                if(i > 0)
                {
                    color[i][i][t] = CAT;
                    q.push(vector<int>({i, i, t, CAT}));
                }
            }

        // BFS过程
        while(!q.empty())
        {
            vector<int> node = q.front();
            q.pop();
            int i = node[0], j = node[1], t = node[2], c = node[3]; // c 颜色
            for(vector<int> parent: _parents(graph, i, j, t))
            {
                int i2 = parent[0], j2 = parent[1], t2 = parent[2];
                if(color[i2][j2][t2] == DRAW)
                {
                    if(t2 == c)
                    {
                        // 父状态节点可以做必胜移动:
                        // 出队状态颜色是猫胜且父节点轮猫走
                        // 出队状态颜色是鼠胜且父节点轮鼠走
                        color[i2][j2][t2] = c;
                        q.push(vector<int>({i2, j2, t2, c}));
                    }
                    else // 当前轮到行动的角色与出队节点的颜色相反
                    {
                        draw_degree[i2][j2][t2]--; // 该父节点又有了一个子节点(出队节点v)不标记为 DRAW
                        if(draw_degree[i2][j2][t2] == 0)
                        {
                            color[i2][j2][t2] = 3 - t2;
                            q.push(vector<int>({i2, j2, t2, 3 - t2}));
                        }
                    }
                }
            }
        }
        return color[1][2][1];
    }

private:
    // 哪些状态节点可以转移到 (m, n, t)
    vector<vector<int> > _parents(vector<vector<int> >& graph, int m, int c, int t)
    {
        vector<vector<int> > result;
        if(t == 2) // 轮猫动, 上一轮轮猫动
        {
            for(int m2: graph[m])
                result.push_back(vector<int>({m2, c, 3 - t}));
        }
        else // 轮鼠动,上一轮轮猫动
        {
            for(int c2: graph[c])
                if(c2 > 0)
                    result.push_back(vector<int>({m, c2, 3 - t}));
        }
        return result;
    }
};

算法2:minimax + 记忆化搜索

Minimax算法常用于棋类等由两方较量的游戏。该算法是一个零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,另一方则选择令对手优势最小化的选择。它是很多博弈型动态规划的基础

dp[m][c][t] := t 时刻,老鼠位于m, 猫位于c的结果。(染色BFS的做法是把 (m, c, t%2) 作为状态,dp[m][c][t%2] 的值作为颜色,一起放入BFS队列中维护)

  • 如果当前轮到老鼠行动,可以到dp[i][c][t+1],其中 i 属于 graph[m],若这些 dp[i][c][t+1] 的结果都为2,则老鼠输了,若其中有一个是1,则老鼠赢,否则平局。
  • 如果当前轮到猫行动,可以到dp[m][j][t+1],其中 j 属于 graph[c],若这些 dp[m][j][t+1] 的结果都为1,则猫输了,若其中有一个是2,则猫赢,否则平局。
  • 初始化:dp[0][c][t] = 1, dp[m][m][t] = 2
  • 若t=2n时游戏仍未结束,则平局。(需要证明)
  • 记忆化搜索的接口
int solve(const vector<vector<int>>& graph, int t, int x, int y, vector<vector<vector<int> > >& dp)

代码(c++)

class Solution {
public:
    int catMouseGame(vector<vector<int>>& graph) {
        int n = graph.size();
        vector<vector<vector<int> > > dp(2 * n, vector<vector<int>>(n, vector<int>(n, -1)));
        return solve(graph, 0, 1, 2, dp);
    }

private:
    int solve(const vector<vector<int>>& graph, int t, int m, int c, vector<vector<vector<int> > >& dp)
    {
        if (t == (int)graph.size() * 2)
            return 0;
        if (m == c)
            return dp[t][m][c] = 2;
        if (m == 0)
            return dp[t][m][c] = 1;
        if (dp[t][m][c] != -1)
            return dp[t][m][c];


        bool flag;
        if (t % 2 == 0)
        {
            // 轮到鼠走
            flag = true;
            for (int i = 0; i < (int)graph[m].size(); i++)
            {
                int nxt = solve(graph, t + 1, graph[m][i], c, dp);
                if (nxt == 1)
                    return dp[t][m][c] = 1;
                else if (nxt != 2)
                    flag = false;
            }
            if (flag)
                return dp[t][m][c] = 2;
            else
                return dp[t][m][c] = 0;
        }
        else
        {
            // 轮到猫走
            flag = true;
            for (int i = 0; i < (int)graph[c].size(); i++)
                if (graph[c][i] != 0) {
                    int nxt = solve(graph, t + 1, m, graph[c][i], dp);
                    if (nxt == 2)
                        return dp[t][m][c] = 2;
                    else if (nxt != 1)
                        flag = false;
                }
            if (flag)
                return dp[t][m][c] = 1;
            else
                return dp[t][m][c] = 0;
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值