洛谷 p1141 01迷宫 //DFS BFS 优化高效

传送门:https://www.luogu.org/problemnew/show/P1141#sub

题目大意:

有一个仅由数字000与111组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)

输入样例#1: 

2 2
01
10
1 1
2 2

输出样例#1: 

4
4

分割线:


这题因为给的数据,n≤1000,m≤100000 较大,所以正常情况下BFS 和 DFS 都会超时,这里记录了3种方法,另外网上还有一种查并集的方法。

本题是其实是求以01可以相互到达的连通分量,互相到达,就是(i,j)如果可以到(k,l),那么(k,l)也必点可以到(i,j),所以同一连通分量中各个点所能到达的格子数目相等。

先介绍BFS算法吧:

本题也是推荐用BFS来写,比较朴实。只需把每一整块连通块看成一个整体,再用一个a数组去记录这个

连通块的值就可以了。

#include<bits/stdc++.h>
using namespace std;
char mp[1010][1010];//地图 
int flag[1010][1010];//判断这个点是否被查找过。 
int n,m,sum;
struct node{
	int x,y;
};
queue<node> q;
int a[1000001];//记录连通块的值 
int dr[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};//4个方向 
void bfs(){ 
	int d=0;
	for(int i = 1;i<=n;i++){ //遍历整张图的点 
		for(int j = 1;j <= n;j++){
			if(flag[i][j]==0){  //此点在之前没有被用到过,单独的联通分量 
				d++;//记录多少个连通块 
				node v;v.x=i;v.y=j;
				q.push(v);
				flag[i][j] = d;
				sum = 1;//此连通块的总个数 
				while(!q.empty()){ 
					node u = q.front();
					q.pop();
					int nx,ny;
					for(int k = 0;k<4;k++){
						nx = u.x + dr[k][0];
						ny = u.y + dr[k][1];
						if(flag[nx][ny]==0 && nx>=1 && ny>=1 && nx<=n && ny<=n && mp[nx][ny]!=mp[u.x][u.y])
						{
							sum ++;
							flag[nx][ny] = d; //x,y级x,y所能到的点都属于本连通块 
							node v2;v2.x = nx;v2.y = ny;
							q.push(v2);
						}
					}
					a[d]=sum;//第d块连通的总数为sum; 
				}
			}
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i = 1;i <= n;i++)
	for(int j = 1;j <= n;j++)
	cin>>mp[i][j];  //读入 
	
	bfs();
	
	for(int i=0;i<m;i++){
		int sx,sy;//读入每次要判断的值直接访问 
		cin>>sx>>sy;
		cout<<a[flag[sx][sy]]<<endl;//只要知道此点属于第几连通块即可 
	}
	return 0;
} 

分割线:


接下来是DFS算法,这种在本题比较容易超时,也是优化了几次后才可以,dfs第一次优化是记录的以前查找过的点,并且类似BFS中将属于同一连通块的点染成相同的,但是对于m,n较大的时候,在每次DFS后刷同一连通块值的时候会循环次数较多超时,即:

for(int i = 0;i < m; ++i){
		int x,y;
		ans=0;
		cin>>x>>y;
		if(bmp[x][y]>0)
		cout<<bmp[x][y]<<endl;
		else{
		dfs(x,y);
		cout<<ans<<endl;

         	for(int j = 1; j <= n;j++)
		for(int k = 1; k <= n;k++)
		if(bmp[j][k]==0) bmp[j][k]=ans;//这里多了一个 m*n*n
		}
	       }

所以这里我们把第一次搜索到的连通块的点作为跟,而其他所有在此连通块的点都指向这个根。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1001;
int mp[maxn][maxn];
int bmp[maxn][maxn];
int genx[maxn][maxn]; //找到此点的根x 坐标 
int geny[maxn][maxn];//找到此点的根y坐标 
int n,m,ans,curx,cury;
int fx[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
void dfs(int x,int y){
	if(x>n || y>n||x<1||y<1) return;
	if(bmp[x][y]!=-1) return; 
	ans++;
	genx[x][y] = curx; //把根符值到x,y点 
	geny[x][y] = cury; 
	 
	bmp[x][y]=0;
	
	for(int i=0;i<4;i++){
	    int nx,ny;
		nx=x+fx[i][0];
		ny=y+fx[i][1];
		if(mp[nx][ny]!=mp[x][y])
		dfs(nx,ny);
	}

}
int main(){
	cin>>n>>m;
	memset(bmp,-1,sizeof(bmp));//这里bmp用-1当作未被遍历过,0用来表示被遍历到,最后用来记录根点所能到达的块数总和 
	for(int i = 1;i <= n; ++i)
	{
		string s;cin>>s;
		for(int j = 0;j<n;j++){
			mp[i][j+1] = s[j]-'0';
		}  //各种方法读入。。 
	} 
	
	
	for(int i = 0;i < m; ++i){
		int x,y;
		ans=0;
		cin>>x>>y;
		if(bmp[genx[x][y]][geny[x][y]]>0) //值判断根节点是否被遍历过就可 
		cout<<bmp[genx[x][y]][geny[x][y]]<<endl;
		else{
		curx = x;cury=y; //新的连通块,新的根 
		dfs(x,y);
		cout<<ans<<endl;

		bmp[x][y] = ans;//这里要把根自己也给符值给自己,避免后面重复提问根 
		genx[x][y] = x;
		geny[x][y] = y;
	       }
	} 
	return 0;	
}

分割线:


最后这种方法也是DFS,只是思路很新奇,并且时间复杂度较低,特此记录。

首先,求连通块应该都知道了,我们用单独的一个数组a[10000001][2]来记录属于当前连通块的所有点的坐标,再统一染值。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int mp[maxn][maxn];//地图 
int f[maxn][maxn]; //值 
bool v[maxn][maxn];//是否走过 
int ans[1000001][2];//记录连通块中点坐标 ,因为m较大,这里的ans需要开大点 
int dr[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int now,n,m;//now用来记录当前连通块中的值 
void dfs(int x,int y){
	now++;//找到这个连通块中所有点 
	ans[now][0]=x;ans[now][1]=y;//记录好这个连通块的各个点坐标,即使和下次地址重复也没事,now会更新 
	for(int i = 0;i < 4;i++){
		int nx = x + dr[i][0];
		int ny = y + dr[i][1];
		if(nx>=1 && ny>=1 && nx<=n && ny<=n && !v[nx][ny]&&mp[nx][ny]!=mp[x][y]){
			v[nx][ny] = 1;//记录走过 
			dfs(nx,ny);
		}
	}
}
int main(){
	cin>>n>>m;
	//memset(v,0,sizeof(v));
	for(int i = 1;i <= n; ++i)
	{
		string s;cin>>s;
		for(int j = 0;j<n;j++){
			mp[i][j+1] = s[j]-'0';
		} 
	}
	
	for(int i = 1;i <= n; ++i) //遍历整张图 
		for(int j = 1;j <= n; ++j){
			if(!v[i][j]){
				v[i][j] = 1;
				now = 0;//每次新的连通块都将now清0 
				dfs(i,j);
				for(int k = 1;k<= now;k++){ //走完一次连通块后 统一染色 
				 f[ans[k][0]][ans[k][1]] = now;
			}
		}
	}
	
	for(int i = 0;i < m;++i){
		int x,y;cin>>x>>y;
		cout<<f[x][y]<<endl; //直接输出记录后的点的值即可 
	}
	return 0;
} 

最后:

progranmming is the most fun you can with your clothes on.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值