博弈 猫和老鼠

猫和老鼠 I

leectcode链接

  • DFS+DP

        普通的做法是考虑步数,当步数达到某个上线maxn,就认为是平局。一般根据抽屉原理,认为上线就是2n^2,即所有的状态数。        

        若上线设置过大,一般会超时,但在leectcode中用2*n就够了。

        因为加上步数维度后,可以把所有状态看成一个节点,连接从一个状态到下一个状态,就可形成一个有向图。

        通过DFS遍历当前状态所有可到达的对手状态,若对手存在必输态,则该状态为必胜态,其次若对手存在平局态,则该状态为平局态,否则为必输态。

int dp[51][51][2][51], maxn;
class Solution {
public:
    vector<vector<int>> g;
    int DFS(int x, int y, int turn, int step){
        if (step == maxn) return 0; //达到上线步数,平局
        if (x == 0) return 1;   //老鼠进洞,老鼠赢,也就是猫输
        if (x == y) return 2;   //猫鼠同一位置,无论谁的回合,都是猫赢
        if (dp[x][y][turn][step] != -1) return dp[x][y][turn][step]; //处理过的状态不再处理
        int ret = -1;
        if (turn == 0){
            bool win = false, draw = false;
            for (auto nx : g[x]){
                int res = DFS(nx, y, 1, step);
                if (res == 1){
                    win = true;
                    break;
                }
                else if (res == 0) draw = true;
            }
            if (win) ret = 1;
            else if (draw) ret = 0;
            else ret = 2;
        }
        else{
            bool win = false, draw = false;
            for (auto ny : g[y]){
                if (ny == 0) continue;
                int res = DFS(x, ny, 0, step + 1);
                if (res == 2){
                    win = true;
                    break;
                }
                else if (res == 0) draw = true;
            }
            if (win) ret = 2;
            else if (draw) ret = 0;
            else ret = 1;
        }
        return dp[x][y][turn][step] = ret;
    }
    int catMouseGame(vector<vector<int>>& G) {
        g = G, maxn = G.size();
        memset(dp, -1, sizeof(dp));
        return DFS(1, 2, 0, 0);
    }
};
  • 拓扑排序+BFS

        该算法应该是最正确的做法,把所有状态抽象成节点(不考虑步数),若一个状态能到另一状态则连上有向边,就可形成一幅有向图。

        有向图分析:

  1. 若图中存在有向环,说明是平局的状态。
  2. 而有些状态节点时可直接得到状态的。如老鼠进洞,猫吃老鼠。这些状态节点出度为0。

        状态推断:

  1. 对于必败态的节点,所有可到达该节点的节点状态都为必胜态
  2. 对于必胜态的节点,可以删除所有可到达该节点的有向边,即让相邻节点的出度减一,当相邻节点的出度为0未处理过时,则相邻节点状态为必败态

        算法步骤:

  1. 计算所有状态节点的出度(即该状态节点可到达所有状态节点数量)。
  2. 把所有确定的状态放入队列。(1)猫鼠同一位置时,猫必胜。(2)老鼠进洞时,猫任意位置,老鼠必胜。
  3. 队列不断弹出,若为弹出结点状态为必败态,将相邻结点置为必胜态,并放入队列。若弹出结点状态为必胜态,将相邻结点的出度减一,当相邻结点出度为0未处理过时,将相邻结点状态置为必败态,并放入队列。
  4. 处理完后,答案就在dp[1][2][0],意思为鼠在1,猫在2,鼠先手的状态,初始状态为0,若到最后都未处理过,就是平局0。
int dp[51][51][2], in[51][51][2];// dp[鼠位置][猫位置][0鼠先手], in 每个状态的出度
class Solution {
public:
    int catMouseGame(vector<vector<int>>& g) {
        int n = g.size();
        memset(dp, 0, sizeof(dp)); //初始状态为0,若到最后都未处理过则为平局0.
        for (int i = 0; i < n; ++i)//谁先手,就谁操作,出度为先手者能到达的下个状态的数量
        for (int j = 1; j < n; ++j)//注意猫不能进洞,故要去掉0的位置
            in[i][j][0] = g[i].size(), in[i][j][1] = g[j].size() - (g[j][0] == 0);
        queue<tuple<int, int, int>> q;
        for (int i = 1; i < n; ++i){//同一位置时,猫主动抓到老鼠,或老鼠送上门。
            dp[i][i][0] = 2, dp[i][i][1] = 2, dp[0][i][1] = 1;//老鼠进洞后,必然是猫操作
            q.emplace(i, i, 0), q.emplace(i, i, 1), q.emplace(0, i, 1);
        }
        while (!q.empty()){
            auto [m, c, op] = q.front(); q.pop();
            if (op == 1){ //该状态是猫先手
                for (auto nm : g[m]){ //说明上个状态是老鼠先手
                    --in[nm][c][op ^ 1]; //减少相邻节点出度
                    if (dp[nm][c][op ^ 1]) continue; //!!!处理过,切记要跳过!!!!
                    if (dp[m][c][op] == 1){ //若为必败态(猫先手,却鼠赢,故必败态)
                        dp[nm][c][op ^ 1] = 1; //相邻节点为必胜态(相邻节点,鼠先手,鼠赢)
                        q.emplace(nm, c, op ^ 1);
                    }
                    else if (in[nm][c][op ^ 1] == 0){ //出度为0且未处理,必败态
                        dp[nm][c][op ^ 1] = 2;    //相邻节点,鼠先手,必败态即是猫赢
                        q.emplace(nm, c, op ^ 1);
                    }
                }
            }
            else{ //处理逻辑类似
                for (auto nc : g[c]){
                    if (nc == 0) continue; //千万要注意猫不能去0位置,即洞里
                    --in[m][nc][op ^ 1];
                    if (dp[m][nc][op ^ 1]) continue;
                    if (dp[m][c][op] == 2){
                        dp[m][nc][op ^ 1] = 2;
                        q.emplace(m, nc, op ^ 1);
                    }
                    else if (in[m][nc][op ^ 1] == 0){
                        dp[m][nc][op ^ 1] = 1;
                        q.emplace(m, nc, op ^ 1);
                    }
                }
            }
        }
        return dp[1][2][0];
    }
};

猫和老鼠 II

leectcode链接

  • DFS+DP

        处理方式和 猫和老鼠 I 类似,只是多了些预处理步骤,不过多解释了。

        题解参考链接

int dp[8][8][8][8][2][80]; //dp[鼠x][鼠y][猫x][猫y][0鼠先手][回合数]
class Solution {
public:
    int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    int n, m, fx, fy, md, cd, MAXN; //fx,fy实物位置。md,cd鼠猫能跳的最远距离。maxn设定最大步数
    vector<string> g;
    int DFS(int mx, int my, int cx, int cy, int turn, int step){
        if (step == MAXN) return 0; // 步数到达上线平局
        if (turn == 1 && mx == fx && my == fy) return 0; //鼠主动吃食,到猫回合,猫输
        if (turn == 0 && cx == fx && cy == fy) return 0; //猫主动吃食,到鼠回合,鼠输
        if (turn == 0 && mx == cx && my == cy) return 0; //猫主动吃鼠,到鼠回合,鼠输
        if (turn == 1 && mx == cx && my == cy) return 1; //鼠送给猫吃,到猫回合,猫输
        if (dp[mx][my][cx][cy][turn][step] >= 0) //处理过直接返回
            return dp[mx][my][cx][cy][turn][step];
        int ret = 0;
        if (turn == 0){//鼠回合
            if (DFS(mx, my, cx, cy, 1, step) == 0) ret = 1;//鼠停留原地,下状态猫输,则鼠赢
            else{
                for (int i = 0; !ret && i < 4; ++i)
                for (int d = 1; !ret && d <= md; ++d){
                    int nx = mx + dir[i][0] * d, ny = my + dir[i][1] * d;
                    if (nx < 0 || nx >= n || ny < 0 || ny >= m || g[nx][ny] == '#') break;
                    if (DFS(nx, ny, cx, cy, 1, step) == 0) ret = 1;
                }
            }
        }
        else{
            if (DFS(mx, my, cx, cy, 0, step + 1) == 0) ret = 1;
            else{
                for (int i = 0; !ret && i < 4; ++i)
                for (int d = 1; !ret && d <= cd; ++d){
                    int nx = cx + dir[i][0] * d, ny = cy + dir[i][1] * d;
                    if (nx < 0 || nx >= n || ny < 0 || ny >= m || g[nx][ny] == '#') break;
                    if (DFS(mx, my, nx, ny, 0, step + 1) == 0) ret = 1;
                }
            }
        }
        return dp[mx][my][cx][cy][turn][step] = ret;
    }
    bool canMouseWin(vector<string>& G, int Cd, int Md) {
        g = G;
        memset(dp, -1, sizeof(dp));
        n = g.size(), m = g[0].size(), cd = Cd, md = Md, MAXN = n * m + 3;
        int mx, my, cx, cy;
        for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            if (g[i][j] == 'M')
                mx = i, my = j;
            else if (g[i][j] == 'C')
                cx = i, cy = j;
            else if (g[i][j] == 'F')
                fx = i, fy = j;
        return DFS(mx, my, cx, cy, 0, 1);
    }
};
  • 拓扑排序+BFS

        操作很复杂,需要预处理很多东西,代码详尽解释。

        题解参考链接

int dp[8][8][8][8][2], in[8][8][8][8][2], step[8][8][8][8][2];
//dp[鼠x][鼠y][猫x][猫y][0鼠先手], in 对应状态的出度
vector<pair<int, int>> neighbor[8][8][2]; //neighbor为猫鼠在各个位置,能到达的下个位置的集合
class Solution {
private:
    static constexpr int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
public:
    bool canMouseWin(vector<string>& g, int cj, int mj) {
        int n = g.size(), m = g[0].size(), MX, MY, CX, CY, fx, fy;
        //MX, MY, CX, CY初始鼠猫位置,fx, fy食物位置
        memset(dp, -1, sizeof(dp));
        for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            if (g[i][j] == 'M')
                MX = i, MY = j;
            else if (g[i][j] == 'C')
                CX = i, CY = j;
            else if (g[i][j] == 'F')
                fx = i, fy = j;
        //获得相邻节点
        auto getNeighbors = [&](int x, int y, int bound) -> vector<pair<int, int>>{
            vector<pair<int, int>> res = {{x, y}};
            for (int i = 0; i < 4; ++i)
            for (int d = 1; d <= bound; ++d){
                int nx = x + dir[i][0] * d, ny = y + dir[i][1] * d;
                if (nx < 0 || nx >= n || ny < 0 || ny >= m || g[nx][ny] == '#') break;
                res.emplace_back(nx, ny);
            }
            return res;
        };
        for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j){//保存猫鼠在各个位置,能到达的下个位置的集合
            if (g[i][j] == '#') continue;
            neighbor[i][j][0] = getNeighbors(i, j, mj);
            neighbor[i][j][1] = getNeighbors(i, j, cj);
        }
        for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j){
            if (g[i][j] == '#') continue;
            for (int k = 0; k < n; ++k)
            for (int l = 0; l < m; ++l){
                if (g[k][l] == '#') continue;//出度!出度!出度!出度!
                in[i][j][k][l][0] = neighbor[i][j][0].size();//鼠操作,鼠可到达的下个位置数
                in[i][j][k][l][1] = neighbor[k][l][1].size();//猫操作,猫可到达的下个位置数
            }
        }

        queue<tuple<int, int, int, int, int>> q;
        for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j){
            if (g[i][j] != '#' && g[i][j] != 'F'){
                dp[i][j][i][j][0] = 0; //猫主动吃鼠,到鼠回合,鼠输
                dp[i][j][i][j][1] = 1; //鼠送给猫吃,都猫回合,猫赢
                q.emplace(i, j, i, j, 0);
                q.emplace(i, j, i, j, 1);
                dp[fx][fy][i][j][1] = 0; //鼠主动吃食,到猫回合,猫输
                dp[i][j][fx][fy][0] = 0; //猫主动吃食,到鼠回合,鼠输
                q.emplace(fx, fy, i, j, 1);
                q.emplace(i, j, fx, fy, 0);
            }
        }

        while (!q.empty()){
            auto [mx, my, cx, cy, turn] = q.front(); q.pop();
            if (turn == 1){//该状态是猫操作,说明上步是鼠操作而来
                for (auto [nx, ny] : neighbor[mx][my][turn ^ 1]){
                    --in[nx][ny][cx][cy][turn ^ 1]; //相邻节点出度减1
                    if (dp[nx][ny][cx][cy][turn ^ 1] != -1) continue; //!!处理过记得跳过!!
                    if (dp[mx][my][cx][cy][turn] == 0){//猫回合,猫输,上状态鼠回合,鼠必赢
                        dp[nx][ny][cx][cy][turn ^ 1] = 1;
                        q.emplace(nx, ny, cx, cy, turn ^ 1);
                    }
                    else if (in[nx][ny][cx][cy][turn ^ 1] == 0){
                        dp[nx][ny][cx][cy][turn ^ 1] = 0; //出度为0,且未处理过,必输
                        q.emplace(nx, ny, cx, cy, turn ^ 1);
                    }
                }
            }
            else{//处理逻辑类似
                for (auto [nx, ny] : neighbor[cx][cy][turn ^ 1]){
                    --in[mx][my][nx][ny][turn ^ 1];
                    if (dp[mx][my][nx][ny][turn ^ 1] != -1) continue;
                    if (dp[mx][my][cx][cy][turn] == 0){
                        dp[mx][my][nx][ny][turn ^ 1] = 1;
                        q.emplace(mx, my, nx, ny, turn ^ 1);
                    }
                    else if (in[mx][my][nx][ny][turn ^ 1] == 0){
                        dp[mx][my][nx][ny][turn ^ 1] = 0;
                        q.emplace(mx, my, nx, ny, turn ^ 1);
                    }
                }
            }
        }
        return dp[MX][MY][CX][CY][0] == 1;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值