【题解】P8655 题解

P8655 题解

思路分析

一道并查集加 DFS 的题。

首先,看到判环这一个要求,我就自然而然地想到了并查集。于是我们可以先用边集数组把所有边存起来。接着遍历,每遍历一条边就判一次环,如果某条边插入后会出现环,则记录这个环的两个顶点 uuvv,不做插入。否则就插入并在并查集中合并。这个操作就相当于把环破坏成链。

接着,根据题意,没有环之后是一个树形结构,也就是两点之间路径唯一。所以可以使用 DFS 遍历从 uuvv 的路径,这个即是环上的所有数。(因为从边 (u,v)(u,v) 破开成链后 uu 为起点,vv 为终点)

按照上述步骤实现即可。这里有个小技巧:因为题目的点的编号在 11nn 之间,所以我们在 DFS 遍历时只需记录访问过的节点,然后输出时就从 11nn 遍历并输出已经访问过的节点就可以实现按序输出,但需注意标记起点。

代码

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;

int fa[N]; //并查集数组
vector <int> g[N]; //vector 存图
struct edge
{
	int fr, to;
} e[N]; //边集数组
bool vis[N]; //访问过的节点
int s, f, n; //起点,终点,题意中的 n

void dfs(int u) //DFS 遍历路径
{
	if(u == f) //搜到了终点
	{
		for(int i = 1;i <= n;i++)
		{
			if(vis[i]) //访问过
			{
				cout << i << " ";
			}
		}
		cout << endl;
		exit(0); //直接退出,因为路径唯一
	}
	for(int i = 0;i < g[u].size();i++) //遍历邻接节点
	{
		int v = g[u][i];
		if(!vis[v]) //如果未访问
		{
            //访问该节点
			vis[v] = true; 
			dfs(v);
            //回溯
			vis[v] = false;
		}
		
	}
	
}
 
int find(int x) //并查集查找祖先函数
{
	return (fa[x] == x ? x : fa[x] = find(fa[x]));
}

void merge(int i, int j) //并查集合并函数
{
	fa[find(j)] = find(i);
}

int main()
{
	cin >> n;
	for(int i = 1;i <= n;i++) fa[i] = i; //并查集初始化
	s = f = 0; //初始化起点、终点 
	int d = 0; //边数
	for(int i = 1;i <= n;i++)
	{
		int x, y;
		cin >> x >> y;
		e[++d].fr = x; //记录边 (u, v) 的 u
		e[d].to = y; //记录 v
	}
	for(int i = 1;i <= d;i++)
	{
		if(find(e[i].fr) == find(e[i].to)) //如果产生环
		{
            //记录起点、终点
			s = e[i].fr;
			f = e[i].to;
		}
		else
		{
			g[e[i].fr].push_back(e[i].to); //插入边
			g[e[i].to].push_back(e[i].fr);
			merge(e[i].fr, e[i].to); //合并
		}
	}
	vis[s] = true; //标记起点
	dfs(s); //DFS 遍历路径
	return 0;
}
posted @ 2023-04-21 08:20  邻补角-SSA  阅读(6)  评论(0)    收藏  举报  来源