原题:
个人感觉并查集是个实现起来短小精悍(指函数代码甚至可以用一行表示)但是原理较为复杂的一种算法。
题意:给定n个点和m条点间的连线,求有多少个由点组成的闭环(除了组成环的连线外这些点没有任何多余的之间的连线或连到无关点的连线)
输入:第一行:给定n,m两个数,n为点数量,编号固定为1,2,3..n。m为边数量
第2至m+1行:输出m行,每行有两个数,代表一条边连接的两个点。
输出:闭环数量
如果读者并不熟悉并查集建议阅读这篇知乎大佬的博客,个人觉得讲的十分通透:算法学习笔记(1) : 并查集 - 知乎 (zhihu.com)
实现代码及解释如下:
#include<bits/stdc++.h>
using namespace std;
struct node{
int v=0; //v表示该节点的连通点数,若不等于2则说明该点所在的集合无法形成闭环
}a[200005]; //表示节点
int fa[200005]; //根节点数组
int find(int x) //查找、更新并返回根节点
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
void merge(int i, int j) //并查集函数(更改i根节点为j的根节点)
{
fa[find(i)] = find(j);
}
bool judge[200005]; //用于标识某根节点所在集合是否为闭环(对普通节点不标)
//全局bool数组默认全0
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){ //初始化根节点
fa[i]=i;
}
for(int i=1;i<=m;i++){ //读入数据的同时进行并查集操作
int t1,t2;
cin>>t1>>t2;
merge(t1,t2);
a[t1].v++; //这两行增加了节点的连通点数
a[t2].v++;
}
for(int i=1;i<=n;i++){
find(i); //更新所有节点的根节点(易错点提醒qwq)
}
long long sum=0;
for(int i=1;i<=n;i++){
if(a[i].v!=2){
judge[fa[i]]=true; //使该点所在的根节点的judge变为true
//使得根节点在之后的判断中被判定为所在集合无法形成闭环
}
}
for(int i=1;i<=n;i++){
if(fa[i]==i&&!judge[fa[i]]){ //若该点为根节点并且judge仍为false
//(集合内所有元素的连通点数都为2)则计入总闭环数。只对根节点进行判断以避免重复计算。
sum++;
}
}
cout<<sum;
return 0;
}
整体思路:使用并查集将所有元素分成题目中给出的一个个集合的样子,每个集合都有一个根节点。然后遍历所有元素,如果有不满足要求(连通其余元素数不为2)的元素则将其父节点代表的集合ban掉(代码实现为judge函数)。最后再次遍历所有元素,查找所有父节点并将
不足之处:其实没有用struct的必要,毕竟就存了个int表示连通点数,相当于int数组...不过也无伤大雅()并且最后两次遍历感觉可以优化。比如最后一次遍历显示是不必要的。因为父节点可以在前几次遍历时用vector(c中使用动态数组)存起来。
最后给出样例,便于复制黏贴测试:
输入 | 输出 |
5 4 1 2 3 4 5 4 3 5 | 1 |
17 15 1 8 1 12 5 11 11 9 9 15 15 5 4 13 3 13 4 3 10 16 7 10 16 7 14 3 14 4 17 6 | 2 |