ABC213 01bfs

E

神秘建图 01bfs/dij

一个网格图,有障碍物,每次可以消除一个2*2区域里的障碍物,问要想到达右下角,最少需要消除多少次?

遇到相邻点是障碍物时,使用一次消除,有多种使用方式,如图
在这里插入图片描述
由于我们关心消除次数,这些绿的点消除次数都为1,可以看作当前点到这些绿的点有一条边权为1的边。对于直接相邻,且没有障碍物的点,边权就是0。

你可能会说,有的绿点可能没有障碍物,但是仍然被连了边权1的边?这对答案无影响,因为如果无障碍物,肯定有一个只由边权0的边组成的路径,也可以到达这个点,最小值会取这个边权0的路径。

在这张图上跑最短路即可。可以dij。但是由于边权只有01,可以用另一种更快的方法:01bfs。具体来说,仍然是个bfs+数组记录最短距离,但不用优先队列了,使用双端队列,我们可以把边权0的点松弛后放进队首,边权1的点放进队尾。

为啥这样是对的?回忆一下我们dij用优先队列的目的是什么,优先队列可以保证距离近的点先被松弛,让最短路尽可能早的被取到,从而距离更长的方案,就不会被入队,入队点数少了,复杂度就低了。而在边权只有01的图中,走0边权的边的点,肯定距离更近,我们想让他比走1边权的点先被松弛,于是把他放在队首,把1边权的放在队尾。

最后的复杂度分析,每个点最多被入队两次,复杂度O(n)O(n)O(n),比dijO(nlog⁡n)O(n\log n)O(nlogn)快很多,当然在本题时间很宽裕,两种都能通过。

实现时,需要注意,01bfs一定是根据边权01来决定是放到队首还是队尾的

dij

char a[1000][1000];
void solve(){
	int n,m;
	cin>>n>>m;
	rep(i,1,n){
		rep(j,1,m){
			cin>>a[i][j];
		}
	}
	priority_queue<array<int,3>,vector<array<int,3>>,greater<array<int,3>>>q;
	q.push({0,1,1});
	vi d(n*m+10,1e18);
	d[1]=0;
	
	while(q.size()){
		auto p=q.top();
		q.pop();
		
		int x=p[1],y=p[2],du=p[0];
		int u=(x-1)*m+y;
		if(d[u]<du)continue;
		rep(i,-2,2){
			rep(j,-2,2){
				if(i==0&&j==0)continue;
				int xx=x+i,yy=y+j;
				if(xx<1||xx>n||yy<1||yy>m)continue;
				int dis=abs(i)+abs(j);
				if(dis==4)continue;
				
				int w=1;
				if(dis==1&&a[xx][yy]=='.'){
					w=0;
				}
				int v=(xx-1)*m+yy;
				if(d[v]>d[u]+w){
					d[v]=d[u]+w;
					q.push({d[v],xx,yy});
				}
			}
		}
	}
	cout<<d[n*m];
//	rep(i,1,n){
//		rep(j,1,m){
//			cout<<d[(i-1)*m+j]<<' ';
//		}
//		cout<<'\n';
//	}
}

01bfs

char a[1000][1000];
void solve(){
	int n,m;
	cin>>n>>m;
	rep(i,1,n){
		rep(j,1,m){
			cin>>a[i][j];
		}
	}
	deque<array<int,3>>q;
	q.push_back({0,1,1});
	vi d(n*m*2,1e18);
	d[1]=0;
	
	while(q.size()){
		auto p=q.front();
		q.pop_front();
		
		int x=p[1],y=p[2],du=p[0];
		int u=(x-1)*m+y;
		if(d[u]<du)continue;
		rep(i,-2,2){
			rep(j,-2,2){
				if(i==0&&j==0)continue;
				int xx=x+i,yy=y+j;
				if(xx<1||xx>n||yy<1||yy>m)continue;
				int dis=abs(i)+abs(j);
				if(dis==4)continue;
				
				int w=1;
				if(dis==1&&a[xx][yy]=='.'){
					w=0;
				}
				int v=(xx-1)*m+yy;
				if(d[v]>d[u]+w){
					d[v]=d[u]+w;
					if(w==0)q.push_front({d[v],xx,yy});
					else q.push_back({d[v],xx,yy});
				}
			}
		}
	}
	cout<<d[n*m];
//	rep(i,1,n){
//		rep(j,1,m){
//			cout<<d[(i-1)*m+j]<<' ';
//		}
//		cout<<'\n';
//	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值