hdu 1728 逃离迷宫 普通的队列 / 优先队列

11 篇文章 0 订阅
2 篇文章 0 订阅

题目链接


题意:

给定 m * n 的矩阵与起点终点位置, 限制转弯次数 k, 问能否到达终点


是个很显然的bfs, 但是

1. 要注意的是转弯无论一次转几个方向都是转一次, 一开始想当然的认为如果从(1, 0)方向转向(0, 1)方向就是转了两次, 结果一直WA...

2. 大概是因为是pascal入门所以一直对优先队列没什么概念, 而一直老老实实写普通的队列...但感觉大家都很直接的知道就是应该写优先队列


下面就来说一下两种不同的写法吧

(觉得优先队列写起来真是方便Orz


1. 

如果是写一般的队列, 要注意用 num[i][j][dir] 来记录以方向 dir 来访问 map[i][j] 时用的最少步数, 

不能直接开个 vis[i][j] 来记录是否访问过这个格子; 即使开了 vis[i][j][dir] 记录了是否以特定方向访问过某个格子, 也无法保证是最少的步数.

当且仅当步数少于 num[i][j][dir] 时将这个 node 给 push 进队列, 并且更新 num[i][j][dir].

从队头取出结点时, 如果其转弯数 num > num[i][j][dir] (即意味着在队列中的后面有更少的步数), 那就对其不作处理, 直接 pop 出去.

思路还是挺清楚的.

此外, 因为一直不能保证当前的 num 为最小值 (至少在我一开始以为一次性可以转两次的情况下...), 所以需要跑完整张图, 但是100 * 100还是很小的, 也没关系



AC代码如下:

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
#define inf 0x3f3f3f3f
#define maxn 110
using namespace std;
struct node {
    int x, y, num, dir;
};
queue<node> q;
const int dx[4][2] =  {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int num[maxn][maxn][4], a[maxn][maxn], m, n;
char s[maxn][maxn];
inline min(int a, int b) { return a < b ? a : b; }
void work() {
    for (int i = 1; i <= m; ++i) {
        scanf("%s", s[i]);
    }
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (s[i][j - 1] == '.') a[i][j] = 0;
            else a[i][j] = 1;
        }
    }
    int k, x1, y1, x2, y2;
    scanf("%d%d%d%d%d", &k, &y1, &x1, &y2, &x2);
    memset(num, -1, sizeof(num));
    for (int i = 0; i < 4; ++i) {
        num[x1][y1][i] = 0;
        int tx = x1 + dx[i][0], ty = y1 + dx[i][1];
        if (tx > 0 && tx <= m && ty > 0 && ty <= n && !a[tx][ty]) {
            node temp;
            temp.x = tx; temp.y = ty; temp.num = 0; temp.dir = i;
            num[tx][ty][i] = 0;
            q.push(temp);
        }
    }

    while (!q.empty()) {
        node nowf = q.front();
        int x = nowf.x, y = nowf.y, nnum = nowf.num, d = nowf.dir;
//        printf("%d %d\n", x, y);
        if (nnum <= num[x][y][d]) {
            for (int i = 0; i < 4; ++i) {
                int tx = x + dx[i][0], ty = y + dx[i][1];
                if (tx > 0 && tx <= m && ty > 0 && ty <= n && !a[tx][ty]) {
                    int tot;
                    if (d == i) tot = nnum;
                    else tot = nnum + 1;
//                    int tot = min(abs(i - d), abs(4 - (i - d))) + nnum;
                    if (num[tx][ty][i] == -1 || tot < num[tx][ty][i]) {
                        num[tx][ty][i] = tot;
                        node temp;
                        temp.x = tx; temp.y = ty; temp.num = tot; temp.dir = i;
                        q.push(temp);
                    }
                }
            }
        }
        q.pop();
    }
    int ans = inf;

    for (int i = 0; i < 4; ++i) if (num[x2][y2][i] != -1) ans = min(ans, num[x2][y2][i]);
    if (ans <= k) printf("yes\n");
    else printf("no\n");
//    printf("%d\n", ans);
}
int main() {
//    freopen("1728.in", "r", stdin);
    int t;
    scanf("%d", &t);
    while (scanf("%d%d\n", &m, &n) != EOF) work();
    return 0;
}


2.

优先队列的话就可以保证一点, 那就是,

在下一次以这个方向访问这个节点时, 其值必然大于等于上一次的值,

故在取出队头时, 一旦其为终点, 必然就是转弯次数最少的结果, 就可以结束了.

因此, 有一点要注意, 就是跑下一组数据时, 必须要先 while (!q.empty()) q.pop();

并且此时就可以放心地用 vis[i][j][dir] 来表示是否以这个方向访问过这个位置了, 因为第一次访问必然是最优的访问

写起来也很好写 (挺短


AC代码如下:

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
#define inf 0x3f3f3f3f
#define maxn 110
using namespace std;
struct node {
    int x, y, num, dir;
    node(int xx = 0, int yy = 0, int nn = 0, int dd = 0) : x(xx), y(yy), num(nn), dir(dd) {}
    bool operator < (const node& b) const { return num > b.num; }
};
priority_queue<node> q;
const int dx[4][2] =  {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int a[maxn][maxn], m, n;
bool vis[maxn][maxn][4];
char s[maxn][maxn];
inline min(int a, int b) { return a < b ? a : b; }
void work() {
    for (int i = 1; i <= m; ++i) {
        scanf("%s", s[i]);
    }
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (s[i][j - 1] == '.') a[i][j] = 0;
            else a[i][j] = 1;
        }
    }
    memset(vis, 0, sizeof(vis));
    int k, x1, y1, x2, y2;
    scanf("%d%d%d%d%d", &k, &y1, &x1, &y2, &x2);
    while (!q.empty()) q.pop();
    for (int i = 0; i < 4; ++i) q.push(node(x1, y1, 0, i));
    while (!q.empty()) {
        node nowf = q.top(); q.pop();
        int x = nowf.x, y = nowf.y, num = nowf.num, d = nowf.dir;
//        printf("%d %d %d\n", x, y, num);
        if (num > k) { printf("no\n"); return; }
        if (x == x2 && y == y2) { printf("yes\n"); return; }
        if (vis[x][y][d]) continue;
        vis[x][y][d] = true;
        for (int i = 0; i < 4; ++i) {
            int tx = x + dx[i][0], ty = y + dx[i][1];
            if (tx > 0 && tx <= m && ty > 0 && ty <= n && !a[tx][ty] && !vis[tx][ty][d]) {
                int tot = num;
                if (d != i) ++tot;
                q.push(node(tx, ty, tot, i));
            }
        }
    }
    printf("no\n");
}
int main() {
//    freopen("1728.in", "r", stdin);
    int t;
    scanf("%d", &t);
    while (scanf("%d%d\n", &m, &n) != EOF) work();
    return 0;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值