BFS(双端队列+状态压缩) - 拯救大兵瑞恩 - HDU 4845

BFS(双端队列+状态压缩) - 拯救大兵瑞恩 - HDU 4845

题意:

给 定 n × m 的 迷 宫 , 起 点 在 左 上 角 ( 1 , 1 ) , 终 点 在 右 下 角 ( n , m ) 。 给定n×m的迷宫,起点在左上角(1,1),终点在右下角(n,m)。 n×m(1,1)(n,m)

两 个 点 之 间 有 三 种 连 通 方 式 : 墙 ( 不 连 通 ) , 门 ( 需 要 钥 匙 才 能 通 过 ) , 直 接 连 通 。 两个点之间有三种连通方式:墙(不连通),门(需要钥匙才能通过),直接连通。 ()()

首 行 输 入 三 个 整 数 n , m , p , n 和 m 表 示 迷 宫 的 行 和 列 , p 是 冗 余 条 件 。 首行输入三个整数n,m,p,n和m表示迷宫的行和列,p是冗余条件。 n,m,pnmp

再 输 入 一 行 整 数 k , 表 示 门 和 墙 的 总 数 , 接 着 输 入 k 行 数 据 , 每 行 包 括 两 个 坐 标 ( x 1 , y 1 ) , ( x 2 , y 2 ) 和 c , 再输入一行整数k,表示门和墙的总数,接着输入k行数据,每行包括两个坐标(x_1,y_1),(x_2,y_2)和c, kk(x1,y1),(x2,y2)c

c = 0 时 , 表 示 ( x 1 , y 1 ) , ( x 2 , y 2 ) 之 间 有 墙 , 即 不 连 通 。 c=0时,表示(x_1,y_1),(x_2,y_2)之间有墙,即不连通。 c=0(x1,y1),(x2,y2)

c > 0 时 , 表 示 ( x 1 , y 1 ) , ( x 2 , y 2 ) 之 间 有 门 , c 为 门 的 编 号 ( 与 钥 匙 要 对 应 ) 。 c>0时,表示(x_1,y_1),(x_2,y_2)之间有门,c为门的编号(与钥匙要对应)。 c>0(x1,y1),(x2,y2)c()

然 后 输 入 整 数 s , 表 示 钥 匙 的 数 量 , 最 后 输 入 s 行 数 据 , 然后输入整数s,表示钥匙的数量,最后输入s行数据, ss

每 行 包 括 x , y , q , 表 示 在 ( x , y ) 处 有 q 号 门 的 钥 匙 。 每行包括x,y,q,表示在(x,y)处有q号门的钥匙。 x,y,q,(x,y)q

现 要 求 从 左 上 角 到 右 下 角 的 最 短 距 离 。 现要求从左上角到右下角的最短距离。

Sample Input

4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1

Sample Output

14

样 例 如 下 图 : 样例如下图:
在这里插入图片描述
数据范围:

1 ≤ n , m , p ≤ 10 , ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ = 1 , 0 ≤ c ≤ p , 1 ≤ q ≤ p , 1 ≤ k ≤ 150 T i m e   l i m i t : 1000 m s , M e m o r y   l i m i t : 32768 k B 1≤n,m,p≤10,|x_1−x_2|+|y_1−y_2|=1,0≤c≤p,1≤q≤p,1≤k≤150\\Time \ limit:1000 ms,Memory \ limit:32768 kB 1n,m,p10x1x2+y1y2=10cp1qp1k150Time limit:1000msMemory limit:32768kB


分析:

若 没 有 钥 匙 限 制 , 可 以 直 接 b f s 得 到 最 短 路 。 若没有钥匙限制,可以直接bfs得到最短路。 bfs

加 上 钥 匙 的 限 制 , 我 们 需 要 拆 点 , 进 行 状 态 压 缩 。 加上钥匙的限制,我们需要拆点,进行状态压缩。

由 于 钥 匙 的 数 量 不 超 过 10 , 故 我 们 用 一 个 长 度 为 10 的 二 进 制 数 来 表 示 当 前 位 置 有 哪 些 编 号 的 钥 匙 。 由于钥匙的数量不超过10,故我们用一个长度为10的二进制数来表示当前位置有哪些编号的钥匙。 1010

为 了 方 便 , 我 们 将 二 维 坐 标 映 射 到 一 维 坐 标 , 每 个 点 ( x , y ) 给 一 个 编 号 i d 。 为了方便,我们将二维坐标映射到一维坐标,每个点(x,y)给一个编号id。 便(x,y)id

距 离 数 组 d i s [ i ] [ s t a t e ] 表 示 从 源 点 到 点 i 且 当 前 拥 有 的 钥 匙 为 s t a t e 的 最 短 距 离 。 距离数组dis[i][state]表示从源点到点i且当前拥有的钥匙为state的最短距离。 dis[i][state]istate

记 当 前 点 的 编 号 为 i d , 状 态 为 c u r , 与 之 相 连 通 的 点 的 编 号 为 j , 则 分 两 种 情 况 : 记当前点的编号为id,状态为cur,与之相连通的点的编号为j,则分两种情况: idcurj

① 、 当 前 位 置 有 钥 匙 k e y [ i d ] : 最 优 解 一 定 是 拿 起 钥 匙 , 花 费 为 0 , 则 状 态 变 为   s t a t e = c u r   ∣   k e y [ i d ] , ①、当前位置有钥匙key[id]:最优解一定是拿起钥匙,花费为0,则状态变为\ state=cur\ |\ key[id], key[id]0 state=cur  key[id]

即 d i s [ j ] [ s t a t e ] = d i s [ i d ] [ c u r ] 。 \qquad即dis[j][state]=dis[id][cur]。 dis[j][state]=dis[id][cur]

② 、 向 周 围 四 个 方 向 扩 展 ( 直 接 连 通 或 者 有 门 且 有 对 应 的 钥 匙 ) : ②、向周围四个方向扩展(直接连通或者有门且有对应的钥匙): ()

判 断 能 否 开 门 : 当 前 位 置 的 门 的 编 号 w [ i ] > 0 , 表 示 有 门 , 若 状 态 c u r 的 对 应 的 位 置 上 不 为 1 , 表 示 没 钥 匙 , 不 连 通 。 \qquad判断能否开门:当前位置的门的编号w[i]>0,表示有门,\\\qquad若状态cur的对应的位置上不为1,表示没钥匙,不连通。 w[i]>0cur1

否 则 两 点 连 通 , 判 断 通 过 i d 再 到 j 是 否 更 近 : 若 d i s [ j ] > d i s [ i d ] + 1 , 则 更 新 d i s [ j ] = d i s [ i d ] + 1 , 将 j 入 队 继 续 扩 展 。 \qquad否则两点连通,判断通过id再到j是否更近:\\\qquad若dis[j]>dis[id]+1,则更新dis[j]=dis[id]+1,将j入队继续扩展。 idjdis[j]>dis[id]+1dis[j]=dis[id]+1j

建图:

本 题 建 图 也 有 难 度 。 本题建图也有难度。

首 先 我 们 将 n × m 的 矩 阵 中 的 所 有 点 依 次 编 号 , 映 射 到 一 维 坐 标 上 来 。 首先我们将n×m的矩阵中的所有点依次编号,映射到一维坐标上来。 n×m

接 着 我 们 根 据 读 入 的 门 和 墙 先 建 立 一 条 无 向 边 , 并 将 该 边 加 入 到 集 合 中 去 。 接着我们根据读入的门和墙先建立一条无向边,并将该边加入到集合中去。

然 后 我 们 遍 历 整 个 图 , 通 过 集 合 判 断 , 将 相 邻 之 间 没 有 边 的 两 点 之 间 建 立 一 条 边 , 权 值 置 为 0 , 表 示 连 通 。 然后我们遍历整个图,通过集合判断,将相邻之间没有边的两点之间建立一条边,权值置为0,表示连通。 0

求解最短路:

由 于 边 权 仅 存 在 0 ( 拿 钥 匙 ) 和 1 ( 移 动 位 置 ) 两 种 情 况 , 故 我 们 可 以 用 双 端 队 列 广 搜 来 解 决 , 时 间 复 杂 度 是 线 性 的 。 由于边权仅存在0(拿钥匙)和1(移动位置)两种情况,故我们可以用双端队列广搜来解决,时间复杂度是线性的。 0()1()广线

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<deque>
#include<set>

#define P pair<int,int>
#define x first
#define y second

using namespace std;

const int N=11, M=N*N, E=N*(N-1)*2*2, S=1<<10;

int n,m,p,k;
int h[M],e[E],ne[E],w[E],idx;
int g[N][N];
int dis[M][S],key[M];
bool st[M][S];
set<P> edge;

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void build()
{
    int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int u=0;u<4;u++)
            {
                int x=i+dir[u][0], y=j+dir[u][1];
                if(x<1||x>n||y<1||y>m) continue;
                int a=g[i][j],b=g[x][y];
                if(!edge.count({a,b})) add(a,b,0);   //不存在时再建边
            }
}

int bfs()
{
    memset(dis,0x3f,sizeof dis);
    dis[1][0]=0;
    
    deque<P> dq;
    dq.push_front({1,0});   //first:编号,second:状态
    
    while(dq.size())
    {
        P t=dq.front();
        dq.pop_front();

        if(t.x==n*m) return dis[t.x][t.y];
        
        int id=t.x,cur=t.y;
        if(st[id][cur]) continue;
        st[id][cur]=true;
        
        if(key[id])   //当前位置有钥匙,拿起来
        {
            int state=cur|key[id];
            dis[id][state]=dis[id][cur];
            dq.push_front({id,state});
        }
            
        for(int i=h[id];~i;i=ne[i])   //向相邻方向扩展
        {
            int j=e[i];    
            if(w[i] && !( cur>>w[i]-1 & 1)) continue;  //有门但没钥匙
            if(dis[j][cur]>dis[id][cur]+1)
            {
                dis[j][cur]=dis[id][cur]+1;
                dq.push_back({j,cur});
            }
        }
    }
    return -1;
}

int main()
{
    cin>>n>>m>>p>>k;
    
    for(int i=1,cnt=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            g[i][j]=cnt++;
    
    memset(h,-1,sizeof h);
    while(k--)
    {
        int x1,y1,x2,y2,t;
        cin>>x1>>y1>>x2>>y2>>t;
        int a=g[x1][y1],b=g[x2][y2];
        edge.insert({a,b}),edge.insert({b,a});
        if(t) add(a,b,t),add(b,a,t);
    }
    
    int kcnt;
    cin>>kcnt;
    while(kcnt--)
    {
        int x,y,t;
        cin>>x>>y>>t;
        key[g[x][y]] |= 1<<(t-1);   //映射为一个二进制数
    }
    
    build();
    
    cout<<bfs()<<endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值