【蓝桥杯 2017 国 B】发现环

【蓝桥杯 2017 国 B】发现环


蓝桥杯专栏:2017 国 B
算法竞赛:图论,树,基环树
题目连接:洛谷 【蓝桥杯 2017 国 B】发现环

题目描述:
小明的实验室有 N N N 台电脑,编号 1 ∼ N 1 \sim N 1N。原本这 N N N 台电脑之间有 N − 1 N-1 N1 条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。
不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了 BUG。
为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?

输入格式:
第一行包含一个整数 N N N
以下 N N N 行每行两个整数 a a a b b b,表示 a a a b b b 之间有一条数据链接相连。
输入保证合法。
输出格式:
按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。

数据范围:
对于 30 % 30\% 30% 的数据, 1 ≤ N ≤ 1000 1 \le N \le 1000 1N1000
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 10 5 1 \le N \le 10^5 1N105 1 ≤ a , b ≤ N 1 \le a,b \le N 1a,bN


发现环(递归回溯法)

题目大意

在一棵树上加上一条边,形成一个环,找出所有在环上的点。

这样的图叫作基环树,基环树是一个由 n n n 个点及 n n n 条边组成的联通图,其是由树增添一条边而来的,由于形成了一个环,所以称作基环树。

思路分析

我们可以先将图 dfs 一遍,找到一个在环上的点,然后从这个点出发,用递归回溯的方式找到在环上的所有顶点。

  1. 通过 dfs 找环上的点时,如果一个图是一棵树,只需要判断向下递归的不是父节点(由此点递归而来)即可,这由树的性质而得。但是现在多了一条边,那么必然在递归的时候有一个顶点要被递归到两次,且这个点肯定在环上,那么我们可以用布尔数组标记一下,如果一个点被递归过然而又要去递归它时,那么这个点就是我们要找的那个在环上的点。
  2. 递归回溯:现在我们已经找到了在环上的一个点,那么我们将它作为递归的起始点 n o d e node node,如果是一颗树,那么从 n o d e node node 点递归最后肯定回不到 n o d e node node 点,但现在 n o d e node node 点在环上,那么按照正常的递归方式(不用布尔数组标记,仅传参父节点)会再次回到 n o d e node node 点,那么从 n o d e node node 点又回到 n o d e node node 点路径上的点就是我们要找的点。
  3. 最后注意:这种递归回溯法只适用于正常逻辑上的由三个或三个以上顶点组成的环,但在图论中还有二元环这种特殊的环,即两个顶点中存在重边(两个 hack 数据均是二元环的测试数据),那么我们再特殊判断一下有没有重边即可(有重边的话就不用递归回溯了,多一条边只可能有一个环)。

AC Code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
vector<int>G[N];
bool ins[N],vis[N];//ins[]--dfs 找环上点用,vis[]-- 递归回溯标记用
int node=0,n,fl;
map<string,int>mp;//判断重边用
void dfs(int x,int fa)
{
	ins[x]=1;
	for (size_t i=0; i<G[x].size(); i++)
	{
		int y=G[x][i];
		if (y==fa) continue;//父节点记得跳过
		if (ins[y])
		{
			node=y;//这就是我们需要的顶点
			continue;
		}
		dfs(y,x);
	}
}
void __dfs(int x,int fa)
{
	vis[x]=1;
	for (size_t i=0; i<G[x].size(); i++)//用 int i 有的编译器会报错
	{
		if (fl) return;//结束递归函数
		int y=G[x][i];
		if (y==fa) continue;
		if (y==node)//完美闭环
		{
			for (int i=1; i<=n; i++)
				if (vis[i]) printf("%d ",i);
			fl=1;
			return;
		}
		__dfs(y,x);
		vis[y]=0;//回溯
	}
}
int main()
{
	int x,y;
	scanf("%d",&n);
	for (int i=0; i<n; i++)
	{
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
		if (mp[to_string(x)+"to"+to_string(y)])//由于记录是记录的双向边,判断的时候判断一边即可
		{
			printf("%d %d",min(x,y),max(x,y));
			return 0;
		}
		else
		{
			mp[to_string(x)+"to"+to_string(y)]=1;
			mp[to_string(y)+"to"+to_string(x)]=1;
		}
	}
	dfs(1,0);
	__dfs(node,0);
	return 0;
}

End

感谢观看,如有问题欢迎指出。

更新日志

  1. 2025/8/29 开始书写本篇 优快云 博客,并完稿发布。

本篇博客最早由本人发布于洛谷文章广场,本篇博客对其进行了修改调整与完善丰富。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HAH-HAH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值