拯救大兵瑞恩(P4011孤岛营救问题)(bfs+状态压缩)

题目描述
     1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形,其南北方向被划分为 N 行,东西方向被划分为 MM列,于是整个迷宫被划分为 N×M 个单元。每一个单元的位置可用一个有序数对(单元的行号,单元的列号)来表示。南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成P类,打开同一类的门的钥匙相同,不同类门的钥匙不同。

    大兵瑞恩被关押在迷宫的东南角,即 (N,M)单元里,并已经昏迷。迷宫只有一个入口,在西北角。也就是说,麦克可以直接进入(1,1) 单元。另外,麦克从一个单元移动到另一个相邻单元的时间为 1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。

    试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。
输入格式
    第一行有三个整数,分别表示 n,m,p 的值。
    第二行是一个整数k,表示迷宫中门和墙的总数。
    第 i+2 行 (1≤i≤k),有 5 个整数,依次为 xi1,yi1,xi2,yi2,gi:当 gi≥1 时,表示 (xi1,yi1)单元与 (xi2,yi2) 单元之间有一扇第 gi 类的门,当 gi=0 时, 表示 (xi1,yi1) 单元与 (xi2,yi2) 单元之间有一堵不可逾越的墙。
    第 k+3 行是一个整数 s,表示迷宫中存放的钥匙总数。
    第 k+3+j 行 (1≤j≤s) ,有 3 个整数,依次为 xi1,yi1,qi ,表示第 j 把钥匙存放在 (xi1,yi1) 单元里,并且第 j 把钥匙是用来开启第 qi 类门。
    输入数据中同一行各相邻整数之间用一个空格分隔。
    |xi1−xi2|+|yi1−yi2|=1,0≤gi≤p|xi1−xi2|+|yi1−yi2|=1,0≤    gi≤p
    1≤qi≤p
    n,m,p≤10,k<150

题解:

    这题目之前搜索专题的时候,不会写,看了下,好像要用网络流,信奥一本通上的解析又没有看懂。然后今天上了洛谷看了下题解,发现好像没有我想的那么复杂,它主要是判重上,和能不能走的问题上有点特殊,判重的话,我开了一个 vector< int > vis [ 16 ] [ 16 ],来标记。至于钥匙的问题,就要用到状态压缩的知识了,可以把每一把钥匙看作成一位二进制数,详细地我推荐洛谷上的这篇知识介绍,我也是看这个的。

https://86971.blog.luogu.org/solution-p4011

 我感觉他/她写得很好
然后就是典型bfs,四个方向走一遍,先判断位置合不合法,如果合法然后判断从目前这个位置到另一个位置是否有墙,或者是否有门
    如果有墙,则走不通。如果有门,没有钥匙也走不通,其他情况都可以走过去。
    我是用mp[16][16][16][16],一个四维数组来保存地图表示(x1,y1)->(x2,y2),刚开初始化为-1,表示都可以走通,然后根据输入改比如有墙是0。
(1 << mp[x1][y1][x2][y2]) 与 当前的key做与操作。假如结果为零说明,他没有这一把钥匙,门打不开。反之则行。
注意点
  • 一个位置可能有多把钥匙,钥匙可以重复使用,初始位置可能也会有钥匙
  • 注意数据范围
#include<bits/stdc++.h>
using namespace std;
int N,M,P,K,S;
int mp[16][16][16][16];
vector<int> key[16][16];//一个地方可能会有多把钥匙
int dir[4][2]={-1,0,0,1,1,0,0,-1};
vector<int> vis[16][16];//用这个数组来保存状态
struct node{
    int x,y;//当前的坐标,和钥匙数//二进制表示钥匙
    int step,k;
}start;
inline bool Catch(node &a)
{
    if(a.x==N&&a.y==M) return true;
    return false;
}
bool check(int x,int y,node a)
{
    if(x<1||x>N||y<1||y>M) return false;
    int t=mp[a.x][a.y][x][y];
    if(t==-1) return true;
    else if(t==0) return false;
    else{
        if(((1<<t)&a.k)>0) return true;  //加括号不然会出错
        else return false;
    }
}
int bfs()
{
    start.x=1,start.y=1,start.step=0;
    for(int i=0;i<key[1][1].size();++i)
        start.k|=1<<key[1][1][i];
    vis[1][1].push_back(start.k);
    queue<node>Q;
    Q.push(start);
    while(!Q.empty())
    {
        start=Q.front();Q.pop();
        if(Catch(start)) return start.step;
        for(int i=0;i<4;++i)
        {
            node r=start;
            int x=r.x+dir[i][0],y=r.y+dir[i][1];
            if(check(x,y,r)==false) continue;//此路不通
              r.x=x,r.y=y,r.step++;
            for(int j=0;j<key[x][y].size();++j)
                r.k|=(1<<key[x][y][j]); //加括号,不然出错了
            int tag=0;
            for(int j=0;j<vis[r.x][r.y].size();++j)
              if(vis[r.x][r.y][j]==r.k) {tag=1;break;} //已经访问过了
            if(tag)continue;
            if(Catch(r)) return r.step;
            vis[r.x][r.y].push_back(r.k);
            Q.push(r);
        }
    }
    return -1;
}
int main()
{
    //int N,M,P,K,S;
    memset(mp,-1,sizeof(mp)); //-1表示周围是连通的,可以走
    cin>>N>>M>>P;
    cin>>K;
    int x1,y1,x2,y2,g,q;
    for(int i=1;i<=K;++i)
    {
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);
        mp[x1][y1][x2][y2]=g;  //双向通道
        mp[x2][y2][x1][y1]=g;
    }
    cin>>S;
    for(int j=1;j<=S;++j)
    {
        scanf("%d%d%d",&x1,&y1,&q);
        key[x1][y1].push_back(q);
    }
    cout<<bfs();
    return(0);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值