3828 行走路径(记忆化搜索 + 找环)

1. 问题描述:

给定一个 n × m 的方格矩阵。每个方格中都包含一个大写字母:Q,W,E,R 之一。现在,小明要在方格矩阵中进行移动。
具体移动规则如下:
最初,小明应选择某个包含字母 Q 的方格作为起点。
小明每次移动可以沿上下左右四个方向,移动一格距离。当然不能出界。
小明在移动时,必须遵循:从包含字母 Q 的方格只能移动到包含字母 W 的方格,从包含字母 W 的方格只能移动到包含字母 E 的方格,从包含字母 E 的方格只能移动到包含字母 R 的方格,从包含字母 R 的方格只能移动到包含字母 Q 的方格。
小明每走过一个完整的 QWER,就称小明走过了一个轮次。小明需要走过尽可能多的轮次。即统计路径中完整的QWER的个数,例如WERQWERQ中仅有1个个QWER。
请问小明最多可以走过多少轮次?

输入格式

第一行包含两个整数 n,m。接下来 n 行,每行包含 m 个字符,每个字符为 Q,W,E,R 之一。

输出格式

如果小明无法成功走过任何轮次,则输出 none。如果小明可以成功走过无穷多轮次,则输出 infinity。如果小明可以成功走过有限多轮次,则输出他能走过的最多轮次。

数据范围

前六个测试点满足 1 ≤ n,m ≤ 5。
所有测试点满足 1 ≤ n,m ≤ 1000。

输入样例1:

1 2
QW

输出样例1:

none

输入样例2:

2 2
ER
WQ

输出样例2:

infinity

输入样例3:

5 5
QWERQ
QWERW
QWERE
QQERR
RREWQ

输出样例3:

4
来源:https://www.acwing.com/problem/content/description/3831/

2. 思路分析:

分析题目可以知道我们需要找到包含"QWER"最多的路径,首先可以选择一个起点然后从当前起点出发开始找到包含"QWER"最长的字符串,因为需要找到所有方案中最长的路径所以需要使用dfs来搜索所有方案(dfs可以无死角进行搜索),因为数据规模为10 ^ 6所以需要使用记忆化搜索,记忆化搜索需要借助于一个f数组来记录递归过程中对应状态的值,一般题目中有多少个状态那么就需要声明多少维的f数组,对于这道题目来说因为只涉及到矩阵的横坐标与纵坐标所以声明两维的f数组即可,f[x][y]表示从(x,y)这个位置搜索能够搜到的最长路径长度,一开始的时候将所有f数组的元素标记为-1,表示还未搜索过,后面在求解的过程中更新对应位置的值即可,如果在搜索的过程中发现之前已经搜索过了那么直接返回对应位置的值即可;这道题目由于存在环所以需要在图中判断是否存在环,判断图中是否存在环一般有四种方法:① 拓扑排序;② 强联通分量/双连通分量;③ spfa判环;④ dfs遍历的过程判环;对于这道题目来说由于使用dfs来搜索,所以可以使用在dfs遍历节点的时候判断是否存在环,我们可以借助一个st数组来记录递归过程中的路径,如果往下递归的过程中发现下一个位置之前已经访问过说明肯定存在环,使用一个全局变量is_inf来记录是否存在环,找到环之后直接返回即可。因为这道题目输入的是字符串,为了方便后面搜索我们可以将字符串"QWER"对应为0123的数字,这样在dfs的过程就很方便并且计算路径长度也很方便。

3. 代码如下:

因为数据量在10 ^ 6,所以对于python语言来说递归深度是很大的,会发生爆栈的问题,最后一个数据没过:

import sys
from typing import List


class Solution:
    # is_inf为是否存在环的标志
    is_inf = res = None
    # pos为右左下上四个方向
    pos = [[0, 1], [0, -1], [1, 0], [-1, 0]]

    def dp(self, x: int, y: int, n: int, m: int, g: List[List[int]], st: List[List[int]], f: List[List[int]]):
        # 存在环返回-1
        if self.is_inf: return -1
        # 之前已经搜索过那么直接返回f[x][y]
        if f[x][y] != -1: return f[x][y]
        st[x][y] = 1
        v = 1
        for i in range(4):
            a, b = x + self.pos[i][0], y + self.pos[i][1]
            if 0 <= a < n and 0 <= b < m and (g[x][y] + 1) % 4 == g[a][b]:
                # 说明找到了环
                if st[a][b] == 1:
                    self.is_inf = 1
                    return -1
                # 从当前(x, y)->(a, b)搜索的最大长度, 求解上下四个方向哪个方向的路径长度最大
                v = max(v, self.dp(a, b, n, m, g, st, f) + 1)
        # 恢复现场, 后面才可以搜索这些位置
        st[x][y] = 0
        # 将当前的值赋值给f[x][y], 表示当前位置往下搜索最后有多少个格子
        f[x][y] = v
        return v

    def process(self):
        n, m = map(int, input().split())
        # g存储QWER对应0123的数字方面后面的dfs搜索
        g = list()
        for i in range(n):
            s = input()
            t = list()
            for c in s:
                if c == "Q":
                    t.append(0)
                elif c == "W":
                    t.append(1)
                elif c == "E":
                    t.append(2)
                else:
                    t.append(3)
            g.append(t)
        self.is_inf = self.res = 0
        # 记忆化搜索需要使用一个数组f来记录递归的中间结果, st用来记录当前递归路径的节点
        st, f = [[0] * (m + 10) for i in range(n + 10)], [[-1] * (m + 10) for i in range(n + 10)]
        # 枚举所有的起点开始往下搜索的最长路径
        for i in range(n):
            for j in range(m):
                t = self.dp(i, j, n, m, g, st, f)
                if self.is_inf: return "infinity"
                # 减去一开始不符合要求的开始字符
                if g[i][j]: t -= 4 - g[i][j]
                # 每4个字符为一轮所以需要除以4
                self.res = max(self.res, t // 4)
        if self.res == 0: return "none"
        return self.res


if __name__ == '__main__':
    # 设置递归的最大深度
    sys.setrecursionlimit(400010)
    print(Solution().process())

c++:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
char g[N][N];
bool st[N][N];
int f[N][N];
bool is_inf;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dp(int x, int y)
{
    if (is_inf) return -1;
    int& v = f[x][y];
    if (v != -1) return v;
    st[x][y] = true;

    v = 1;
    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a >= 0 && a < n && b >= 0 && b < m && g[a][b] == (g[x][y] + 1) % 4)
        {
            if (st[a][b])
            {
                is_inf = true;
                return -1;
            }

            v = max(v, dp(a, b) + 1);
        }
    }

    st[x][y] = false;
    return v;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%s", g[i]);
        for (int j = 0; j < m; j ++ )
        {
            auto& c = g[i][j];
            if (c == 'Q') c = 0;
            else if (c == 'W') c = 1;
            else if (c == 'E') c = 2;
            else c = 3;
        }
    }

    memset(f, -1, sizeof f);
    int res = 0;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            int t = dp(i, j);
            if (g[i][j]) t -= 4 - g[i][j];
            res = max(res, t / 4);
        }

    if (is_inf) puts("infinity");
    else if (!res) puts("none");
    else printf("%d\n", res);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值