CF510B 两点(DFS/BFS/并查集判环)

题目描述:

题目传送门


解题思路:

一道在地图上连通块中判环的题目,我们可以考虑用 d f s dfs dfs b f s bfs bfs 以及并查集这三种方法来判环。

  • 深搜判环

由于环的本质上就类似于一条头尾相接的链,因此用“一路走到底” 的DFS是最合适不过的了。

我们每个点都尝试延伸,向与它同颜色的一个点一直往下走,直道找到一条长度大于4的路径,使得他们头尾相连,那么这就是一条符合题目要求的环。

代码难度也不高,就是爆搜。

#include <bits/stdc++.h>
using namespace std;
int n,m;
char g[101][101];
const int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
bool vis[101][101]={false};
bool dfs(int dep,int x,int y,char co,int sx,int sy)  //SX SY表示最开始的点的位置
{
	if(dep>n*m) return 0;  //环上的点显然不会大于所有点的个数
	if(x==sx&&y==sy&&dep>=4)  //判断是否是条环
	  return true;
	for(int i=0;i<4;i++)
	  {
	  	int nx=x+dx[i],ny=y+dy[i];
	  	if(nx<=0||nx>n||ny<=0||ny>m) continue;
	  	if(g[nx][ny]!=co) continue;
	  	if(nx==sx&&ny==sy&&dep>=4) return true;
	  	if(vis[nx][ny]) continue;
	  	vis[nx][ny]=true;
	  	if(dfs(dep+1,nx,ny,co,sx,sy))  //搜索
	  	  return true;
	  	vis[nx][ny]=false;
	  }
	return false;
}
int main()
{
	while(cin>>n>>m)
	  {
	  	memset(vis,0,sizeof(vis));
	  	for(int i=1;i<=n;i++)
	  	  {
	  	  	for(int j=1;j<=m;j++)
	  	  	  cin>>g[i][j];
		  }
		bool f=false;
		for(int i=1;i<=n;i++)
		  {
		  	for(int j=1;j<=m;j++)
		  	  {
		  	  	vis[i][j]=true;
		  	  	if(dfs(1,i,j,g[i][j],i,j))
		  	  	  {
		  	  	  	cout<<"Yes"<<endl;
		  	  	  	f=true;
		  	  	  	break;
				  }
				vis[i][j]=false;
			  }
			if(f) break;
		  }
		if(!f) cout<<"No"<<endl;
	  }
	return 0;
}
  • 广搜判环

在这种题目上其实广搜判环是比不上深搜判环的,但由于也是一种做法,所以我们也得掌握一下。
其实思路也很简单,我们暴走一遍广搜,但是不能走回头路(也就是说不能走到上一个节点),在这种不往回走的情况下,一直向前,走到一个我们访问过的点,也就表示收尾相连了,那么这就是一个环了

#include <bits/stdc++.h>
using namespace std;
int n,m;
char g[101][101];
const int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
bool vis[101][101]={false};
struct Gar
{
	int x,y;
	int fax,fay;
	Gar(int xx,int yy,int faax,int faay):x(xx),y(yy),fax(faax),fay(faay) {}
	//FAX FAY表示上一个点的坐标
};
queue<Gar> q;
bool bfs(int sx,int sy)
{
	q.push(Gar(sx,sy,-1,-1));
	while(!q.empty())
	  {
	  	Gar u=q.front();
	  	vis[u.x][u.y]=true;
	  	q.pop();
	  	for(int i=0;i<4;i++)
	  	  {
	  	  	int nx=u.x+dx[i],ny=u.y+dy[i];
	  	  	if((nx<=0||nx>n||ny<=0||ny>m)||(nx==u.fax&&ny==u.fay)) continue;
	  	  	//如果即将要走的位置超出地图或者走了回头路,回到了上一个节点,那么跳过。
	  	  	if(g[nx][ny]!=g[u.x][u.y]) continue;  //如果不同颜色也跳过
	  	  	if(vis[nx][ny])  //找到一个访问过的就是回路
	  	  	  return true;
	  	  	else q.push(Gar(nx,ny,u.x,u.y));
		  }
	  }
	return false;
}
int main()
{
	while(cin>>n>>m)
	  {
	  	memset(vis,0,sizeof(vis));
	  	for(int i=1;i<=n;i++)
	  	  {
	  	  	for(int j=1;j<=m;j++)
	  	  	  cin>>g[i][j];
		  }
		int i,j;
		for(i=1;i<=n;i++)
		  {
		  	for(j=1;j<=m;j++)
		  	  {
		  	  	if(vis[i][j]) continue;
		  	  	if(bfs(i,j))
		  	  	  {
		  	  	  	while(!q.empty()) q.pop();
		  	  	  	//注意队列一定要清零,否则在上一个是yes时,会保留上一次的内容,会错。
		  	  	  	cout<<"Yes"<<endl;
		  	  	  	break;
				  }
			  }
			if(j<=m) break;
		  }
		if(i>n||j>m) cout<<"No"<<endl;
	  }
	return 0;
}
  • 并查集判环

假设有这么一张图:

在这里插入图片描述
一开始,这三个点是处于三个不同的集合中的。也就是说,连接前, A A A B B B 各自在不同集合,合并使其在相同集合。
连接前 A A A C C C 各自在不同的集合,合并使其在相同集合。
最后我们考虑将 C C C B B B 连接,发现,即使还未连接 C   B C~B C B 已经在同一个集合,再连接,说明构成环。

在这里插入图片描述

因此我们考虑便利这张图上的所有节点 ( i , j ) (i,j) (i,j),此时当它们颜色相同时,每个节点可以考虑和它左边 ( i , j − 1 ) (i,j-1) (i,j1) 的节点的集合和上边 ( i − 1 , j ) (i-1,j) (i1,j)的节点所在的集合连接,也就是集合合并。
此时,若有一对 ( i , j ) (i,j) (i,j),当它连接了 ( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i,j-1) (i,j1) 任意一点的时候,他就已经与另外一个点处于相同集合了,那么就如上所述,它们构成了环。

此做法就是用并查集扫描图中的连通块,然后判断连通块的构成是否为环。

#include <bits/stdc++.h>
using namespace std;
int n,m;
char g[101][101];
int fa[100010]={0};
int find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
void join(int x,int y)
{
	int a=find(x),b=find(y);
	fa[a]=fa[b];
	return ;
}
int main()
{
	while(cin>>n>>m)
	  {
	  	for(int i=1;i<=n;i++)
	  	  {
	  	  	for(int j=1;j<=m;j++)
	  	  	  {
	  	  	  	cin>>g[i][j];
	  	  	  	fa[(i-1)*m+j]=(i-1)*m+j; 
	  	  	  	//将地图线性化,更加容易进行并查集操作。
			  }
		  }
		bool flag=false;
		for(int i=1;i<=n;i++)
		  {
		  	for(int j=1;j<=m;j++)
		  	  {
		  	  	if(g[i][j]==g[i][j-1])  //颜色相同才能合并
		  	  	  {
		  	  	  	int a=find((i-1)*m+j),b=find((i-1)*m+j-1);
		  	  	  	if(a==b) flag=true;  //若还未合并就已经属于同一个集合了,就构成环
		  	  	  	join(a,b);
				  }
				if(g[i][j]==g[i-1][j])
				  {
				  	int a=find((i-2)*m+j),b=find((i-1)*m+j);
		  	  	  	if(a==b) flag=true;
		  	  	  	join(a,b);
				  }
			  }
		  }
		if(flag) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	  }
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值