洛谷P5195-Knights-of-Ni-S-题解

Knights of Ni S

题目描述

贝茜遇到了一件很麻烦的事:她无意中闯入了森林里的一座城堡,如果她想回家,就必须穿过这片由骑士们守护着的森林。为了能安全地离开,贝茜不得不按照骑士们的要求,在森林寻找一种特殊的灌木并带一棵给他们。

当然,贝茜想早点离开这可怕的森林,于是她必须尽快完成骑士们给的任务,贝茜随身带着这片森林的地图,地图上的森林被放入了直角坐标系,并按 $x,y $ 轴上的单位长度划分成了 $ W \times H\ ( 1 \leq W,H \leq 1000 )$ 块,贝茜在地图上查出了她自己以及骑士们所在的位置,当然地图上也标注了她所需要的灌木生长的区域。某些区域是不能通过的(比如说沼泽地,悬崖,以及食人兔的聚居地)。在没有找到灌木之前,贝茜不能通过骑士们所在的那个区域,为了确保她自己不会迷路,贝茜只向正北、正东、正南、正西四个方向移动(注意,她不会走对角线)。她要走整整一天,才能从某块区域走到与它相邻的那块区域。

贝茜希望你能帮她计算一下,她最少需要多少天才可脱离这可怕的地方?输入数据保证贝茜一定能完成骑士的任务。

输入格式

第一行输入 22 个用空格隔开的整数,即题目中提到的 $ W,H $。

接下来输入贝茜持有的地图,每一行用若干个数字代表地图上对应行的地形。第一行描述了地图最北的那一排土地;最后一行描述的则是最南面的。相邻的数字所对应的区域是相邻的。如果地图的宽小于或等于 4040,那每一行数字恰好对应了地图上的一排土地。如果地图的宽大于 4040,那每行只会给出 4040 个数字,并且保证除了最后一行的每一行都包含恰好 4040 个数字。没有哪一行描述的区域分布在两个不同的行里。

地图上的数字所对应的地形:

  • 00:贝茜可以通过的空地;
  • 11:由于各种原因而不可通行的区域;
  • 22:贝茜现在所在的位置;
  • 33:骑士们的位置;
  • 44:长着贝茜需要的灌木的土地。

输出格式

输出一个正整数,即贝茜最少要花多少天才能完成骑士们给的任务。

样例 #1

样例输入 #1

8 4
4 1 0 0 0 0 1 0
0 0 0 1 0 1 0 0
0 2 1 1 3 0 4 0
0 0 0 4 1 1 1 0

样例输出 #1

11

提示

这片森林的长为 88,宽为 44.贝茜的起始位置在第 33 行,离骑士们不远。

贝茜可以按这样的路线完成骑士的任务:北,西,北,南,东,东,北,东,东,南,南。她在森林的西北角得到一株她需要的灌木,然后绕过障碍把它交给在东南方的骑士。

P5195 [USACO05DEC] Knights of Ni S - 洛谷

思路

这题是一道很经典搜索题了,考虑代码复用的情况下,可以用一个变量来判断是否找到灌木。~~你要是复制粘贴,改个东西也行。~~其他的搜索内容都和一般搜索题差别不大。

需要注意的是,这题需要搜索到从起点开始,能走到的灌木点的距离和这些灌木点到骑士位置的距离。

一开始我想到的就是用一个向量存能走到的灌木点,然后再以这些灌木点为起点,搜索到骑士位置的距离,再判断他们的最小值。但这样时间复杂度很大,有两个点会TLE,但是开O2优化可以过

两个点会TLE,开O2可过的完整代码(就算用快读快写,数组模拟队列,也很难不TLE)

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie();
    cout.tie();
    int n, m;
    cin >> n >> m;
    vector<vector<bool> > visited(m);
    auto fillFalse = [&] {
        for (auto &i: visited) {
            i.resize(n);
            fill(i.begin(), i.end(), false);
        }
    };
    vector<vector<int> > mat(m);
    vector<pair<pair<int, int>, int> > trees(0);
    pair<int, int> start;
    for (int i = 0; i < m; ++i) {
        mat[i].resize(n);
        for (int j = 0; j < n; ++j) {
            cin >> mat[i][j];
            if (mat[i][j] == 2)
                start = {i, j};
        }
    }
    int dx[]{1, -1, 0, 0}, dy[]{0, 0, 1, -1};
    auto bfs = [&]
            (const int &target) {
        fillFalse();
        queue<pair<pair<int, int>, int>> q;
        q.emplace(start, 0);
        visited[start.first][start.second] = true;
        int x, y, d;
        while (not q.empty()) {
            x = q.front().first.first, y = q.front().first.second, d = q.front().second;
            q.pop();
            if(target == 3 and mat[x][y] == 3)
                break;
            for (int i = 0; i < 4; ++i) {
                int px = x + dx[i], py = y + dy[i];
                if (px < 0 or py < 0 or px >= m or py >= n
                    or visited[px][py]
                    or (target != 3 and mat[px][py] == 3)
                    or mat[px][py] == 1)
                    continue;
                visited[px][py] = true;
                q.emplace(pair<int, int>{px, py}, d + 1);
                if(mat[px][py] == target and target != 3)
                    trees.emplace_back(pair<int, int>{px, py}, d + 1);
            }
        }
        if(target == 3)
            return d;
        return -1;
    };
    bfs(4);
    int ans = INT32_MAX;
    for(auto tree : trees) {
        start = tree.first;
        ans = min(ans, tree.second + bfs(3));
    }
    cout << ans;
    return 0;
}

这样肯定就不好玩了,那我们想想,哪里可以优化。可以发现,我们每次以这些灌木点为起点,搜索到骑士位置的距离,都是一个耗时的过程,那我们不如反过来想想,如果从求骑士点出发到每个灌木点的距离,这样就可以减少搜索的时间复杂度啦,为了方便,我们引用map,方便映射两个点(二维数组也可以,但是我嫌空间复杂度大)。这样,我们修改代码。提交。欸!AC啦!

修改后的C++代码

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie();
    cout.tie();
    int n, m;
    cin >> n >> m;
    vector<vector<bool> > visited(m);
    auto fillFalse = [&] {
        for (auto &i: visited) {
            i.resize(n);
            fill(i.begin(), i.end(), false);
        }
    };
    vector<vector<int> > mat(m);
    map<pair<int, int>, int> trees1, tress2;
    pair<int, int> start, goal;
    for (int i = 0; i < m; ++i) {
        mat[i].resize(n);
        for (int j = 0; j < n; ++j) {
            cin >> mat[i][j];
            if (mat[i][j] == 2)
                start = {i, j};
            if (mat[i][j] == 3)
                goal = {i, j};
        }
    }
    int dx[]{1, -1, 0, 0}, dy[]{0, 0, 1, -1};
    auto bfs = [&](const bool &isFind,
                   map<pair<int, int>, int> &trees) {
        fillFalse();
        queue<pair<pair<int, int>, int>> q;
        q.emplace(start, 0);
        visited[start.first][start.second] = true;
        int x, y, d;
        while (not q.empty()) {
            x = q.front().first.first, y = q.front().first.second, d = q.front().second;
            q.pop();
            for (int i = 0; i < 4; ++i) {
                int px = x + dx[i], py = y + dy[i];
                if (px < 0 or py < 0 or px >= m or py >= n
                    or visited[px][py]
                    or (mat[px][py] == 3 and not isFind)
                    or mat[px][py] == 1)
                    continue;
                visited[px][py] = true;
                q.emplace(pair<int, int>{px, py}, d + 1);
                if (mat[px][py] == 4)
                    trees[pair<int, int>{px, py}] += d + 1;
            }
        }
    };
    bfs(false, trees1);
    start = goal;
    bfs(true, tress2);
    int ans = INT32_MAX;
    for (auto &tree: trees1)
        if (tress2[tree.first] != 0)
            ans = min(ans, tree.second + tress2[tree.first]);
    cout << ans;
    return 0;
}

AC截图


洛谷P5195-Knights-of-Ni-S-题解
https://winterl-blog.netlify.app/2023/07/23/洛谷P5195-Knights-of-Ni-S-题解/
作者
winterl
发布于
2023年7月23日
许可协议