D106 01BFS最短路+状压 P4011 孤岛营救问题
D106 01BFS最短路+状压 P4011 孤岛营救问题_哔哩哔哩_bilibili
格子有 N 行 M 列,格子间有墙有门,有 p 种门 p 种钥匙,先捡钥匙再开门,问从 (1,1) 到 (N,M) 的最短路。

思路
格点间的转移与拥有多少种钥匙的状态有关,所以我们把每个格点拆成 $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左

例如,$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)$ 的条件:① 是通道,② 有门且有匹配的钥匙

相关板子:
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(); }
浙公网安备 33010602011771号