本来F题选的是一道线段树+懒标记的题
题意是,有许多个点,有一些点之间是互相联通的,有些点之间是不联通的(如图)
图中的点分成了三块,显然,我们只需要加入两条路,即可将这三个区块的点合为一块
也就是说,我们只要确定这些点分成了N块,就只需要加上N-1条路就能将他们连接在一起
所以就需要一种数据结构-----并查集
将上图中的点标上号
我们开一个数组,其中各个数是
1号位的数是1,说明1号位的"源点"或者称中心点为1
听不懂?看接下来的模拟过程
如上图,1点和3点之间有一条路,也就是说1,3点是联通的
我们设3点作为源点,于是将1号位上的数字指向三号位,此刻数组变为
接下来2号点与3号点也有一条路,这次我们把2号点作为3号点的源点,数组变为
1号位的源点是3,3号位的源点是2,这样就能找到1号位的祖宗源点是2
如何用代码来实现寻找:
int find(int x){
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
假如我们要找1的源点
p[x]=3,x=1,所以执行p[x]=find(p[x])语句
递归到下一层,find(p[1])就是find(3),而p[3]!=3,因为p[3]=2,所以再递归一层,find(p[3])就是find(2),此时p[2]=2,成立,返回2,上一层的p[3]=find(p[3])即获得赋值2,返回p[3]再向上传递,p[1]=2,此时向主函数返回2,即是1号点的源点2;
而经过一次find之后,数组此刻变化为
不仅完成了寻找源点,还完成了路径的优化!寻找1的源点的路径从之前的
1->3->2变成了1->2,下一次寻找时所递归的层数就是大大减少
利用并查集,我们可以将所有点区分开来,完成时数组为
此时我们只需要看其中有几个不相同的数即可判断有几个区块
#include <iostream>
#include <set>
using namespace std;
const int N = 100010;
int p[N];
int find(int x) {
if (x != p[x])p[x] = find(p[x]);
return p[x];
}
int main() {
set<int>s;//set的特性是会自动去重
int n, m, i, j;
cin >> n >> m;
for (int i = 1; i <= n; i++)
p[i] = i;
for (int k = 1; k <= n; k++) {
cin >> i >> j;
int ii = find(i), jj = find(j);//分别找出i和j的源点
if (ii != jj) {
p[ii] = jj;//将i节点的源点指向j节点的源点
}
}
for (int i = 1; i <= n; i++) {
s.insert(find(i));
}
cout << s.size() - 1 << endl;
}