【题解】AT_abc293_d 题解

AT_abc293_d 题解

思路分析

一道并查集裸题。

为什么要使用并查集呢?因为题目要求连接两条绳子,同时还要判环,所以我们把绳子抽象成点,连接两条绳子抽象为用一条边连接两个点。于是就可以参考克鲁斯卡尔算法的思路,使用并查集来判环:如果两条绳子已经被连接在一条链(由绳子连接而成的连通分量)上的话,又被连接一次,就产生了环,此时产生了环的链数加一。

那么题目又要求统计非环链数,又该怎么办呢?很简单,同样使用并查集统计所有链数,一个祖先代表一条链。然后用总链数减去产生了产生了环的链数就可以了。接着按照上述步骤实现即可,注意并查集要初始化。

代码

#include <iostream>
using namespace std;
const int N = 2e5 + 10;
 
int fa[N]; //并查集父亲数组
bool vis[N]; //判断祖先是否重复的数组
 
int find(int x) //寻找祖先
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
 
void init(int p) //初始化并查集
{
	for(int i = 1;i <= p;i++)
	{
		fa[i] = i;
	}
}
 
void merge(int i, int j) //合并两条绳子
{
	fa[find(i)] = find(j);
}
 
 
int main()
{
	int n, m;
	cin >> n >> m;
	int x = 0, z = 0; //x 用于记录有环个数,z 用来记录总链数
	init(n); //初始化并查集
	for(int i = 1;i <= m;i++)
	{
		int q, p;
		char c;
		cin >> q >> c >> p >> c; 
		if(find(q) != find(p)) //如果不在同一个链上
		{
			merge(q, p); //合并
		}
		else
		{
			x++; //已经在同一个链上,又要连接一次,就会形成环,此时有环个数加一。
		}
	}
    for(int i = 1;i <= n;i++) //遍历所有点,分别查找其祖先
    {
        int r = find(i); //查找祖先
        if(!vis[r]) //如果祖先没有计算过
        {
            z++; //计算该祖先,一个祖先代表一条链。
            vis[r] = true; //标记改祖先已被访问。
        }
    }
	cout << x << " " << z - x << endl; //输出答案,非环链数为总链数减有环链数的差
	return 0;
}
posted @ 2023-03-13 22:06  邻补角-SSA  阅读(12)  评论(0)    收藏  举报  来源