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,j−1) 的节点的集合和上边
(
i
−
1
,
j
)
(i-1,j)
(i−1,j)的节点所在的集合连接,也就是集合合并。
此时,若有一对
(
i
,
j
)
(i,j)
(i,j),当它连接了
(
i
−
1
,
j
)
(i-1,j)
(i−1,j) 或
(
i
,
j
−
1
)
(i,j-1)
(i,j−1) 任意一点的时候,他就已经与另外一个点处于相同集合了,那么就如上所述,它们构成了环。
此做法就是用并查集扫描图中的连通块,然后判断连通块的构成是否为环。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!