题解 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实现,要修改风格。主要就是,状态扩展的时候,要有面向对象设计程序的思想。尽管这并不是本题的重点,但却是程序设计的重点。
另一方面,总结归纳状态表示与判断的技巧和核心思路。

posted @ 2025-07-30 11:32  枯骨崖烟  阅读(11)  评论(0)    收藏  举报