先吐槽一下,在循环压缩路径时,若采取以下代码,路径压缩之后 树的深度有2,明明深度只增加一层,却直接超时了,运行时间直接增加了至少10倍 ◔ ‸◔?
int find(int x) { int a = x; while (p[x] != -1) x = p[x]; while (p[a] != x) //这里改了 { int t = p[a]; p[a] = x; a = t; } return x; }
现在进入正题:
目前,学了并查集学了一周多了,想着总结一下自己所学,感觉并查集虽然不是属于很复杂的数据类型,却也是设计的很精妙,可以运用于许多地方。
一,先讲一下 parent 数组的初始化为,我开始学的时候,是全部初始化为-1 ,但是学长们都是初始化为 p[i]=i
做到现在,也没感觉有什么差别,只是 find 这个函数,-1的话,注意不要查询到 根节点的父节点了,就好了。
另外还有一点,就是初始化为 -1 的话,无法用递归压缩路径,因为 你最后返回不是根结点。
题目:http://www.fjutacm.com/Problem.jsp?pid=2592
初始化为 -1 的代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 1000000+10 int p[N], n, m; int find(int x) { int a = x; while (p[x] != -1) x = p[x]; while (p[a] != -1) { int t = p[a]; p[a] = x; a = t; } return x; } int Find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } void join(int x, int y) { x = find(x), y = find(y); if (x == y) return; p[x] = y; } int main(void) { //while (scanf("%d%d", &n, &m) != EOF) scanf("%d%d", &n, &m); { for (int i = 0; i <= n + 5; i++) p[i] = -1; while (m--) { int a, b; scanf("%d%d", &a, &b); int ans = find(a); if (ans <= b) { printf("%d ", ans); p[ans] = ans + 1; } else puts("-1"); } } system("pause"); return 0; }
二,可以利用根节点记录 集合中元素的个数,集合中最大或最小的数值,这样可以利用压缩路径,减少查询时间
另外,集合的个数可以利用 是否成环的个数 进行计算
三,集合的区域划分(自己命名的 )
题目:http://www.fjutacm.com/Problem.jsp?pid=2351
题目:http://www.fjutacm.com/Problem.jsp?pid=2022
由于并查集是根据数据的编号进行 join 的,这样的话若是 编号重复了(T^T的图论)或者 编号与编号的对应含义不同(食物链)
于是我们可以在数据范围之外再开一组,
如:T^T的图论
,要标记出现的行列,只需要 x join y 就可以了,但是他们的数据出现了重叠
于是 我们可以让 0~n 给 x, n~2n 给 y,然后 x join y+n ,就解决了数据的重复意义。
// 文件名出现 T^T的图论 出现这个 ^ 直接找不到路径打不开,好坑 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> using namespace std; int a[500005], p[100005]; int Find(int x) { return p[x] == x ? x : p[x] = Find(p[x]); } int find(int x) { int a = x; while (x != p[x]) x = p[x]; while (a != x) { int t = p[a]; p[a] = x; a = t; } return x; } void join(int x, int y) { x = find(x), y = find(y); if (x != y) p[x] = y; } int main(void) { int n; while (scanf("%d", &n) != EOF) { for (int i = 1; i <= 100000; i++) p[i] = i; int b; for (int i = 1; i <= n; i++) { scanf("%d%d", &a[i], &b); join(a[i], b + 50000); } int flag = 1; for (int i = 2; i <= n; i++) { if (find(a[i]) != find(a[i - 1])) { flag = 0; break; } } if (flag) puts("YES"); else puts("NO"); } return 0; }
如:食物链 ,
你要 join 两个点,但这两个点却有四种含义关系,直接 join 的话,你又怎么知道是什么关系
于是我们可以分三个区间,这样就有 四种区别了
我们默认
同一区间的为同类
第一区间 吃 第二区间
第二区间 吃 第三区间
第三区间 吃 第一区间
这样的话,
find(x): 找 x 的同类
find(x+n): 找 x 的猎物
find(x+2*n): 找 x 的猎物的猎物,同时也是 x 的天敌
同时
x 与 y 同类 就有 三种情况
join(x, y);
join(x + n, y + n);
join(x + 2 * n, y + 2 * n);
x 吃 y 也有三种情况
join(x + n, y); // x 的猎物是 y
join(x, y + 2 * n); // x 的天敌是 y
join(x + 2 * n, y + n); // x的天敌 是 y的猎物
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define N 50000+5 int p[N * 3]; // g++ 不能这样?只能 int p[200000+5] 或 p[(50000+5)*3] // 同类,猎物,天敌 int find(int x) { int a = x; while (x != p[x]) x = p[x]; while (x != a) { int t = p[a]; p[a] = x; a = t; } return x; } void join(int x, int y) { x = find(x), y = find(y); ++++++++++++++++++++++++++ if (x == y) return; p[x] = y; } int main(void) { int n, k; scanf("%d %d", &n, &k); { for (int i = 1; i <= n * 3 + 2; i++) p[i] = i; int ans = 0; while (k--) { int d, x, y; scanf("%d %d %d", &d, &x, &y); if (x > n || y > n) { ans++; continue; } if (d == 1) { // x 的猎物是 y 或 x 的天敌是 y if (find(x + n) == find(y) || find(x + 2 * n) == find(y)) { ans++; continue; } // x 的同类是 y join(x, y); join(x + n, y + n); join(x + 2 * n, y + 2 * n); } else // x 吃 y { if (x == y) { ans++; continue; } // x 的同类是 y 或者 y 的猎物是 x if (find(x) == find(y) || find(x) == find(y + n)) { ans++; continue; } join(x + n, y); // x 的猎物是 y join(x, y + 2 * n); // x 的天敌是 y join(x + 2 * n, y + n); // x的天敌 是 y的猎物 } } printf("%d ", ans); } system("pause"); return 0; }
四,压缩路径
通过压缩路径减少查询时间
题目:http://www.fjutacm.com/Problem.jsp?pid=2592
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 1000000+10 int p[N], n, m; int find(int x) { int a = x; while (p[x] != x) x = p[x]; while (a != x) { int t = p[a]; p[a] = x; a = t; } return x; } int Find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } void join(int x, int y) { x = find(x), y = find(y); if (x == y) return; p[x] = y; } int main(void) { //while (scanf("%d%d", &n, &m) != EOF) scanf("%d%d", &n, &m); { for (int i = 0; i <= n + 5; i++) p[i] = i; while (m--) { int a, b; scanf("%d%d", &a, &b); int ans = Find(a); if (ans <= b) { printf("%d ", ans); p[ans] = ans + 1; } else puts("-1"); } } system("pause"); return 0; }
题目:http://www.fjutacm.com/Problem.jsp?pid=2650
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define N 2*100000+5 struct vessel { int c; // 容量 int w; // 现在水的体积 }v[N]; int p[N]; int find(int x) { int a = x; while (x != p[x]) x = p[x]; while (p[a] != x) { int t = p[a]; p[a] = x; a = t; } return x; } void scan(int &x) { char c; do c = getchar(); while (c<'0' || c>'9'); x = c - '0'; while ('0' <= (c = getchar()) && c <= '9') x = x * 10 + c - '0'; } void print(int a) { if (a>9) print(a / 10); putchar(a % 10 + '0'); } int main(void) { int n; scan(n); for (int i = 1; i <= n; i++) { scan(v[i].c); v[i].w = 0; p[i] = i; }p[n + 1] = n + 1, v[n + 1].c = 2147483647 - 1, v[n + 1].w = 0; int t; scan(t); while (t--) { int num; scan(num); if (num == 1) { int x, y; scan(x); scan(y); x = find(x); if (v[x].w + y < v[x].c) v[x].w += y; else while(v[x].w + y >= v[x].c) { int q = find(x + 1); if (q != n + 1) v[q].w += (v[x].w + y - v[x].c); v[x].w = v[x].c; p[x] = q; x = find(x), y = 0; } } if (num == 2) { int x; scan(x); print(v[x].w); puts(""); } } return 0; }
五,还有个移动次数的
见:https://www.cnblogs.com/asdfknjhu/p/12528457.html
六,并查集还可以和其他东西结合
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题
=========== ========= ======== ======= ====== ====== ===== === == =
《沙扬娜拉》 徐志摩
——赠日本女郎
最是那一低头的温柔,
像一朵水莲花不胜凉风的娇羞,
道一声珍重,道一声珍重,
那一声珍重里有蜜甜的忧愁——