📜  门|门CS 2013 |第 41 题(1)

📅  最后修改于: 2023-12-03 15:28:49.170000             🧑  作者: Mango

门 | 门CS 2013 | 第 41 题

题目描述

在一个 $n$ 行 $m$ 列的网格图中,有部分格子障碍着,定义两个格子上下左右相邻,且它们之一没有障碍时为相邻的格子。现在,从图上任选两个相邻的无障碍格子作为起点和终点,你需要求出从起点到终点的最短路,并把这些最短路上的边都取出来。

输入格式: 第一行两个正整数 $n,m$,表示网格图有 $n$ 行 $m$ 列。 接下来 $n$ 行,每行 $m$ 个字符,字符为 .#,其中 . 表示格子上没有障碍,# 表示格子上有障碍。保证第 $1$ 行和第 $n$ 行的字符都是 #,两侧也都是 #,且除了两侧,最多只有一列相邻的 . 格子。

输出格式: 第一行为从起点到终点的最短路长度。 接下来若干行,表示这些最短路上的边,每行两个整数,表示这个边所连接的两个点,用坐标表示,要求输出的边的顺序必须为它们在路径上出现的顺序。

思路

本题是一个最短路问题,可以用 Dijkstra 或 BFS 解决。

对于输出的路径,可以在搜索过程中记录每个点的前一个点,找到终点后,倒推回去输出路径。

领接表存储图、堆优化的 Dijkstra 或者 BFS 实现、路径输出,都是经典算法的基础操作,这里不再详细说明。

代码

以下为 C++ 堆优化的 Dijkstra 实现:

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

using namespace std;

typedef pair<int, int> PII;

const int N = 510, M = N * N;
const int INF = 0x3f3f3f3f;

char g[N][N];    // 存储地图
int n, m;
int idx = 0;     // 存边数组边序号

int h[N], e[M], ne[M], w[M], idx;
bool st[N];
int dist[N], pre[N];

void add(int a, int b, int c)   // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra(int start)
{
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;

    // 堆优化的dijkstra
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        // 相当于松弛操作
        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];

            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                pre[j] = ver;
                heap.push({dist[j], j});
            }
        }
    }
}


int main()
{
    cin >> n >> m;

    int start, end;
    memset(h, -1, sizeof h);

    // Step1:读入地图,建图
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            cin >> g[i][j];

            if (g[i][j] == 'S') start = i * m + j;
            else if (g[i][j] == 'E') end = i * m + j;

            if (g[i][j] == 'S' || g[i][j] == 'E' || g[i][j] == '#') continue;

            int a = i * m + j;      // 非障碍点的编号

            if (i > 0 && g[i - 1][j] == '.')   // 上
            {
                int b = a - m;
                add(a, b, 1);
                add(b, a, 1);
            }

            if (j > 0 && g[i][j - 1] == '.')   // 左
            {
                int b = a - 1;
                add(a, b, 1);
                add(b, a, 1);
            }
        }

    // Step2:跑dijkstra
    dijkstra(start);

    // Step3:输出最短路
    cout << dist[end] << endl;
    if (dist[end] != INF)   // 查找到终点
    {
        int path[N], count = 0;
        for(int i = end; i != start; i = pre[i])
            path[count ++] = i;

        path[count] = start;

        for(int i = count; i >= 1; i --)     // 倒序输出路径
            cout << path[i] / m << " " << path[i] % m << endl;
        cout << path[0] / m << " " << path[0] % m << endl;
    }
    else    // 没有遍历到终点
    {
        cout << -1 << endl;
    }
    return 0;
}

时间复杂度: 打表得最坏情况下网格图中的总障碍数为 $Θ(m \times n)$,因此存储图需要 $Θ(m\times n)$ 的空间,同时添加边数量也为 $Θ(m\times n)$ 级别。 另外,我们需要 $O(m\times n \log m\times n)$ 的时间复杂度求解最短路,由于 $m,n$ 处于一个数量级,可以简单近似为 $Θ(n^2\log n)$。 因此总时间复杂度为 $Θ(m\times n \log m\times n)$,使用堆优化Dijkstra可以保证该复杂度下的计算效率。