1495:【例 2】孤岛营救问题

18 篇文章 1 订阅
7 篇文章 0 订阅

传送门  //因为一本通上有一个测试点错了,所以给洛谷的测试

这题大部分都是用bfs + 状压去做,但是一本通里把这题放在了最短路上,虽然用最短路的时间空间都比bfs要差,但是这是一道很好的去训练分层最短路的题。

BFS版:

bfs把每个种类的钥匙都单独分成一层,然后根据我拿了钥匙的状态去找到我现在应该在的层,再bfs搜索最短路。

#include<bits/stdc++.h>
using namespace std;
const int N = 12;
int dir[5][3]={{0,1},{1,0},{0,-1},{-1,0}};
int e[N][N][N][N],key[N][N][N],dkey[N][N];
int vis[N][N][1<<14];
int n,m;
struct node{
	int x,y,k,d;
	node(){}
	node(int x,int y,int k,int d):x(x),y(y),k(k),d(d){}
};
int getkey(int x,int y){
	int ans = 0;
	for(int i=1;i<=dkey[x][y];i++)
	ans|=(1<<(key[x][y][i]-1));
	return ans;
}
int bfs(){
	queue<node> q;
	int sk = getkey(1,1);
	q.push(node(1,1,sk,0)),vis[1][1][sk] = 1;
	while(q.size()){
		node u = q.front();q.pop();
		if(u.x==n && u.y==m) return u.d;
		int ux = u.x,uy = u.y;
		for(int i=0;i<4;i++){
			int nx = ux+dir[i][0];
			int ny = uy+dir[i][1];
			int opt = e[ux][uy][nx][ny];
			if(nx <1||ny<1||nx>n||ny>m||opt<0||(opt&&!(u.k&(1<<(opt-1))))) continue;
			int nkey = u.k|getkey(nx,ny);
			if(vis[nx][ny][nkey]) continue;
			q.push(node(nx,ny,nkey,u.d+1));
			vis[nx][ny][nkey] = 1;
		}
	}
	return -1;
}
void read(){
	int k,s,r;
	scanf("%d%d%d",&n,&m,&r);
	for(scanf("%d",&k);k--;){
		int x1,y1,x2,y2,g;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);
        if(g) e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=g;
        else e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=-1;
	}
   for(scanf("%d",&s);s--;) {
        int x,y,q;
        scanf("%d%d%d",&x,&y,&q);
        key[x][y][++dkey[x][y]]=q;
    }
} 
int main(){
	read();
    printf("%d\n",bfs());
	return 0;
} 

最短路版:

这里涉及到的变化还蛮多的,我们要先算出最多边数:1>>10*10*10=1024000;

把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,并且图直接有连接。最后跑一遍最短路即可。
ps:要记录总层数的总点数,之后dis[]的初始化时所有点。

//分层求最短路 
#include<bits/stdc++.h>
using namespace std;
const int N = 12;
const int M = 1024100;
const int INF = 0x3f3f3f3f;
typedef pair<int,int> P;
struct keyn{
	int x,y;
}key[N][20];//key[i][j] 种类为i的钥匙第j把的坐标 
int n,m,p,s,k,cnt,layer,nn,nsum;//n宽,m长,p种类,s总钥匙数,k总障碍数,cnt计数器,layer层数,nn每层的点,nsum总点数 
int num[N][N],fg[200][200];
int head[M],nex[M],ver[M],edge[M];
int hadk[N],kn[N],vis[M],dis[M];
void add(int x,int y,int w){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	edge[cnt] = w;
	head[x] = cnt;
}
void read(){
	cnt = 0;
	int x,y;
	scanf("%d%d%d%d",&n,&m,&p,&k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	num[i][j] = ++cnt;
	for(int i=1,g,u,v;i<=k;i++){
		scanf("%d%d",&x,&y); u = num[x][y];
		scanf("%d%d",&x,&y); v = num[x][y];
		scanf("%d",&g);
		if(g==0) g=-1;
		fg[u][v] = fg[v][u] = g;
	}
	scanf("%d",&s);
	for(int i = 1,q;i <= s;i++){
		scanf("%d%d%d",&x,&y,&q);
		kn[q]++;
		key[q][kn[q]].x = x;
		key[q][kn[q]].y = y;
	}
}
void build(){
	layer = 1<<p;
	nn = n*m;
	nsum = n*m*layer;
	for(int t=0;t<layer;t++){
		for(int i=1;i<=p;i++){
			if(t&(1<<(i-1))) hadk[i] = 1;
			else hadk[i] = 0;
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++){
				int u = num[i][j],v = num[i][j+1];//向右连边
				if(v && fg[u][v]!=-1) 
				if(fg[u][v]==0 || hadk[fg[u][v]]){
					add(t*nn+u,t*nn+v,1);
					add(t*nn+v,t*nn+u,1);
				}
				
				v = num[i+1][j]; //向下连边
				if(v && fg[u][v]!=-1) 
				if(fg[u][v]==0 || hadk[fg[u][v]]){
					add(t*nn+u,t*nn+v,1);
					add(t*nn+v,t*nn+u,1);
				}
			}
		for(int i=1;i<=p;i++){
			if(!hadk[i]) //没有钥匙才可以移动状态 
			for(int j=1;j<=kn[i];j++){
				int u = num[key[i][j].x][key[i][j].y];
				add(t*nn+u,( t|(1<<(i-1)) ) *nn+u,0);
			}
		}	
	}
}
void dj(){
	priority_queue<P> q;
	for(int i = 0;i <= nsum; i++) dis[i] = INF;
	q.push(make_pair(0,1)),dis[1] = 0;
	while(q.size()){
		int u = q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(int i = head[u];i;i = nex[i]){
			int v = ver[i],w=edge[i];
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
void spfa(){
	queue<int> q;
	for(int i = 0;i <= nsum;i++) dis[i] = INF;
	q.push(1),dis[1] = 0,vis[1] = 1;
	while(q.size()){
		int u = q.front(); q.pop();vis[u] = 0;
		for(int i=head[u];i;i = nex[i]){
			int v = ver[i],w=edge[i];
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				if(!vis[v]){
					q.push(v),vis[v] = 1;
				}
			}
		}
		
	}
}
void solve(){
	int ans = INF;
	for(int i =0;i<layer;i++)
	ans = min(ans,dis[i*nn+num[n][m]]);
	if(ans==INF) printf("-1\n");
	else printf("%d\n",ans);
}
int main(){
	read();
	build();
	//spfa();
	dj();
	solve();
	return 0;
} 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值