D106 01BFS最短路+状压 P4011 孤岛营救问题

D106 01BFS最短路+状压 P4011 孤岛营救问题_哔哩哔哩_bilibili

 

P4011 孤岛营救问题 - 洛谷

格子有 N 行 M 列,格子间有墙有门,有 p 种门 p 种钥匙,先捡钥匙再开门,问从 (1,1) 到 (N,M) 的最短路。

image

思路

格点间的转移与拥有多少种钥匙的状态有关,所以我们把每个格点拆成 $2^p$ 个状态

或者我们这样设计格点状态 $\{x,y,s,d\}$,$s$ 表示拥有钥匙的种类(用二进制压缩),$d$ 表示当前到起点的距离

我们使用双端队列进行 BFS 扩展,如果 $(x,y)$ 点上有新的钥匙,则新状态 $\{x,y,s|key_{x,y},d\}$ 更具有扩展优势,加入队首

如果能从 $\{x,y,s,d\}$ 走到相邻格点  $(a,b)$,则把新状态 $\{a,b,s,d+1\}$ 加入队尾

1. 用边集数组 $e[x,y,3]$ 保存格点之间的连接:双向通路取-1,墙取0,门大于0。第三维:0下,1右,2上,3左

image

例如,$e[2][4][0]=e[3][4][2]=1,e[1][2][1]=e[1][3][3]=2$

2. 用数组 $key[x,y]$ 保存钥匙种类,因 $p\le10$,用二进制压缩,如 $(x,y)$ 有 1和3 两种钥匙,则 $key[x,y]=(000101)_2$ 

3. 能从 $(x,y)$ 走到相邻格点 $(a,b)$ 的条件:① 是通道,② 有门且有匹配的钥匙

image

相关板子:

B17 双端队列BFS Switch the Lamp On - 董晓 - 博客园

 

// 01BFS最短路+状压 O(NM*2^P)
#include<bits/stdc++.h>
using namespace std;

const int N=11;
int n,m,p,k,s;
int e[N][N][4],key[N][N];
bool vis[N][N][1<<10];
struct node{ //格点状态
  int x,y,s,d; //坐标,钥匙状态,到起点的距离
};
int dx[]{1,0,-1,0},dy[]{0,1,0,-1}; //下,右,上,左

int bfs(){
  deque<node> q;
  q.push_back({1,1,0,0});
  while(!q.empty()){
    auto [x,y,s,d]=q.front(); q.pop_front();
    if(x==n && y==m) return d;
    if(vis[x][y][s])continue; vis[x][y][s]=1;
    
    if((s|key[x][y])!=s) q.push_front({x,y,s|key[x][y],d}); //有新钥匙的状态放队首
    for(int i=0; i<4; ++i){ //向四周移动
      int a=x+dx[i], b=y+dy[i];
      if(a<1||a>n||b<1||b>m) continue;
      if(e[x][y][i]==-1 || e[x][y][i]&&(s&(1<<(e[x][y][i]-1)))) //是通道||有门&&有匹配钥匙
        q.push_back({a,b,s,d+1}); //扩展的状态放队尾
    }
  }
  return -1;
}
int main(){
  cin>>n>>m>>p>>k; //p:门的种类,k:门和墙的总数
  memset(e,-1,sizeof e); //双向通路默认-1
  for(int i=0,x1,y1,x2,y2,g; i<k; ++i){ //存储相邻格点的门或墙
    cin>>x1>>y1>>x2>>y2>>g;
    for(int j=0; j<4; ++j)if(x1+dx[j]==x2&&y1+dy[j]==y2){ //j:0下1右2上3左
      e[x1][y1][j]=e[x2][y2][(j+2)%4]=g; //g=0墙,g>0门
    }
  }
  cin>>s; //s:钥匙总数
  for(int i=0,x,y,q; i<s; ++i){
    cin>>x>>y>>q;
    key[x][y]|=1<<(q-1); //二进制q-1位上保存该种钥匙
  }
  cout<<bfs();
}

 

posted @ 2026-03-12 18:58  董晓  阅读(50)  评论(0)    收藏  举报