题解 luogu.P4011 孤岛营救问题
题目
luogu.P4011 孤岛营救问题
状压+BFS
属于比较简单,思维含量不是很大。
题意建模
求最短路,数据范围也小,边权还是1,所以就是BFS(Bellman_Ford也是一样)。又由于状态不好表示,考虑压缩成二进制。我们重点说一下二进制的表示(也就是解的存在性判断),而不去关注本题的具体实现。当然,主要还有一部分,就是复习一下BFS,所以本题解也会收录进BFS的分类中。
另外,本文还是按照DP的思路去叙述,不过由于搜索与DP同源,逻辑上也是等效的。
算法分析
状态的定义(需要声明的变量)
vis[x][y][S] 表示在 \((x,y)\) 这个位置持有钥匙状态为 \(S\) 是否来过;
BFS队列里存4个变量:
分别为当前横纵坐标,步数以及钥匙状态
a[x1][y1][x2][y2]表示两个点直接是否有墙或者是门。
type[x][y][i]表示 \((x,y)\)这个点放的钥匙的种类
num[x][y]表示当前点钥匙数目
状态转移的判断
考虑一下什么时候有解。由于这很模糊,所以我们考虑这个问题的反面(原解空间的部集)。这个问题的反面就是什么时候无解,我们逐一分析题意,翻译条件,然后做出判断。
- 最强的条件,当存在墙的时候,肯定不能转移;
- 其次,如果存在钥匙,但是与门的类型不相适配,不可转移;
- 最后,转移到的状态不能越界(超越解空间)。
所以排除了以上几种情况之后,才可以扩展状态。
二进制压缩
明确我们状态压缩的是什么。这里压缩的是钥匙的状态。因为很容易就能发现,钥匙的数量,以及所处的位置,都可以作为其状态的元素,这些元素种类繁多,不易表示,所以考虑状态压缩。
在这个地方,我们需要结合代码来分析。
if((condition&1<<t-1)==0) return false; 这一句的判断最好结合上下文,不过我们直接就说了。意义是:
当前钥匙的种类与门的种类是否相同。本题中用到的判断基本上就是这一句这样的。
当前的状态如果
参考代码
#include<iostream>
#include<queue>
using namespace std;
const int N=13;
const int dx[5]={0,1,-1,0,0};
const int dy[5]={0,0,0,1,-1};
struct node{int x,y,step,condition;};
int a[N][N][N][N],type[N][N][N],num[N][N],n,m;
bool vis[N][N][1<<N];
inline bool check(int x1,int y1,int x2,int y2,int condition)
{
if(x2<1||x2>n||y2<1||y2>m) return false;
if(a[x1][y1][x2][y2]==-1) return false;
int t;
if((t=a[x1][y1][x2][y2])!=0)
if((condition&1<<t-1)==0) return false;
return true;
}
int bfs()
{
int temp=0;
for(int i=1;i<=num[1][1];i++)
temp|=1<<type[1][1][i]-1;
vis[1][1][temp]=true;
queue<node> q;
node u=(node){1,1,0,temp}; q.push(u);
while(q.size())
{
u=q.front(); q.pop();
if(u.x==n&&u.y==m) return u.step;
for(int i=1;i<=4;i++)
{
node v={u.x+dx[i], u.y+dy[i], u.step+1, u.condition};
if(!check(u.x, u.y, v.x, v.y, u.condition)) continue;
for(int j=1;j<=num[v.x][v.y];j++)
v.condition|=1<<type[v.x][v.y][j]-1;
if(!vis[v.x][v.y][v.condition])
{
vis[v.x][v.y][v.condition]=true;
q.push(v);
}
}
}
return -1;
}
int main()
{
int p,k,s;
cin>>n>>m>>p;
cin>>k;
for(int i=1;i<=k;i++)
{
int x1,x2,y1,y2,g; cin>>x1>>y1>>x2>>y2>>g;
if(g) a[x1][y1][x2][y2]=a[x2][y2][x1][y1]=g;
else a[x1][y1][x2][y2]=a[x2][y2][x1][y1]=-1; //双向边!!
}
cin>>s;
for(int i=1;i<=s;i++)
{
int x,y,q; cin>>x>>y>>q;
type[x][y][++num[x][y]]=q;
}
cout<<bfs()<<endl;
return 0;
}
细节实现
注意一些细节。
- 钥匙同一个地方,不保证只出现一次(同一个地方可能有多个钥匙);
- 初始点也可以放钥匙
所以注意到,坑点都是一些关键的地方。
总结归纳
BFS实现,要修改风格。主要就是,状态扩展的时候,要有面向对象设计程序的思想。尽管这并不是本题的重点,但却是程序设计的重点。
另一方面,总结归纳状态表示与判断的技巧和核心思路。

浙公网安备 33010602011771号