并查集主要是用来查找图中的节点是否连通。
思路:若想求两点是否连通,只用判断两点所在的树的根节点是否相同。若相同则连通,否则不连通。
主要代码
1 int p[1005]; //p[i]表示节点i的根节点 2 void init() //初始化 3 { 4 for(int i = 0; i < 1005; ++i) p[i] = i; 5 return; 6 } 7 int Find(int x) 8 { 9 return x == p[x] ? x : p[x] = Find(p[x]); 10 } 11 void merge(int a, int b) //合并 12 { 13 int pa = Find(a), pb = Find(b); 14 if(pa == pb) return; //若已经连通,则什么都不用做 15 else p[pa] = pb; //p[pb] = pa 一样 16 return; 17 }
应用:
比如说noip2017day2T1
题目描述
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪 中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中, 奶酪的下表面为z = 0,奶酪的上表面为z = h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐 标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别 地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果 一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在 不破坏奶酪 的情况下,能否利用已有的空洞跑 到奶酪的上表面去?
空间内两点P1(x1,y1,z1)、P2(x2?,y2?,z2?)的距离公式如下:
dist(P1,P2)=sqrt{(x1 * x2)^2 + (y1 * y2)^2 + (z1 * z2)^2}?
输入输出格式
输入格式:
每个输入文件包含多组数据。
输入文件的第一行,包含一个正整数 TT,代表该输入文件中所含的数据组数。
接下来是 TT 组数据,每组数据的格式如下: 第一行包含三个正整数 n,hn,h 和 rr,两个数之间以一个空格分开,分别代表奶酪中空 洞的数量,奶酪的高度和空洞的半径。
接下来的 nn 行,每行包含三个整数 x,y,zx,y,z,两个数之间以一个空格分开,表示空 洞球心坐标为(x,y,z)(x,y,z)。
输出格式:
输出文件包含 TT 行,分别对应 TT 组数据的答案,如果在第 ii 组数据中,Jerry 能从下 表面跑到上表面,则输出Yes
,如果不能,则输出No
(均不包含引号)。
输入输出样例
输入样例
3 2 4 1 0 0 1 0 0 3 2 5 1 0 0 1 0 0 4 2 5 2 0 0 2 2 0 4
Yes No Yes
代码
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 typedef long long ll; 5 int t; 6 ll x[1005], y[1005], z[1005]; 7 int p[1005]; 8 void init() 9 { 10 for(int i = 0; i < 1005; ++i) p[i] = i; 11 return; 12 } 13 int Find(int x) 14 { 15 return x == p[x] ? x : p[x] = Find(p[x]); 16 } 17 void merge(int a, int b) 18 { 19 int pa = Find(a), pb = Find(b); 20 if(pa == pb) return; 21 else p[pa] = pb; 22 return; 23 } 24 int main() 25 { 26 scanf("%d", &t); 27 while(t--) 28 { 29 init(); 30 int n; 31 ll h, r; 32 scanf("%d%lld%lld", &n, &h, &r); 33 for(int i = 1; i <= n; ++i) 34 { 35 scanf("%lld%lld%lld", &x[i], &y[i], &z[i]); 36 for(int j = 1; j < i; ++j) 37 { 38 ll dis = (x[i] - x[j]) * (x[i] - x[j]) + 39 (y[i] - y[j]) * (y[i] - y[j]) + 40 (z[i] - z[j]) * (z[i] - z[j]); 41 if(dis <= 4 * r * r) merge(i, j); 42 } 43 if(z[i] <= r) merge(i, 0); //设下表面是0点,上表面是n + 1点 44 if(z[i] + r >= h) merge(i, n + 1); 45 } 46 47 if(Find(0) == Find(n + 1)) printf("Yes\n"); 48 else printf("No\n"); 49 } 50 return 0; 51 }
最小生成树
就是贪心加上并查集。一条一条加边,知道并查集的树的边加到 n - 1 。则最小生成树各边长度之和就是并查集各边长度之和。
上一道例题:https://www.luogu.org/problemnew/show/3366
代码
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 struct Edge{ 7 int x, y; 8 ll z; 9 bool operator < (const Edge& other)const{ 10 return z < other.z; 11 }; 12 }edge[200005]; 13 int p[5005]; 14 void init() 15 { 16 for(int i = 1; i <= 5000; ++i) p[i] = i; 17 return; 18 } 19 int Find(int x) 20 { 21 return x == p[x] ? x : p[x] = Find(p[x]); 22 } 23 int merge(int a, int b) 24 { 25 int pa = Find(a), pb = Find(b); 26 if(pa == pb) return 0; 27 else 28 { 29 p[pa] = pb; 30 return 1; 31 } 32 } 33 int main() 34 { 35 init(); 36 int n, m, cnt = 0; 37 ll mst = 0; 38 scanf("%d%d", &n, &m); 39 for(int i = 1; i <= m; ++i) 40 { 41 scanf("%d%d%lld", &edge[i].x, &edge[i].y, &edge[i]. z); 42 } 43 sort(edge + 1, edge + m + 1); //从边权小的边开始加 44 for(int i = 1; i <= m; ++i) 45 { 46 if(merge(edge[i].x, edge[i].y) == 1) 47 { 48 cnt++; mst += edge[i].z; 49 } 50 if(cnt == n - 1) 51 { 52 printf("%lld\n", mst); 53 return 0; 54 } 55 } 56 printf("orz\n"); 57 return 0; 58 }