算法-并查集
发表于更新于
字数总计:1.7k阅读时长:7分钟阅读量: 济宁
并查集
1、 将两个集合合并。
2、 询问两个元素是否在一个集合当中。
基本原理:每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。
问题1:如何判断树根 : if(p[x] == x)
问题2:如何求x的集合编号: while (p[x] != x) x = p[x];
问题3: 如何合并两个集合: px是x的集合编号,py是y的集合编号,让p[x] = y, 这样就完成合并了。
合并集合
一共有 nn 个数,编号是 1∼n1∼n,最开始每个数各自在一个集合中。
现在要进行 mm 个操作,操作共有两种:
M a b
,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b
,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b
或 Q a b
中的一种。
输出格式
对于每个询问指令 Q a b
,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例:
1 2 3 4 5 6
| 4 5 M 1 2 M 3 4 Q 1 2 Q 1 3 Q 3 4
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> using namespace std;
const int N = 100010;
int n, m; int p[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
int main() { scanf("%d%d", &n, &m); for (int i = 0; i <= n; i ++ ) p[i] = i; while (m -- ) { char op[2]; int a, b; scanf("%s%d%d", op, &a, &b); if (*op == 'M') p[find(a)] = find(b); else { if (find(a) == find(b)) puts("Yes"); else puts("No"); } } return 0; }
|
路径压缩逻辑:
if (p[x] != x)
:这是一个条件判断语句,p
是一个数组,p[x]
表示元素 x
的父节点。如果 p[x]
不等于 x
,说明 x
不是它所在集合的祖宗节点(因为祖宗节点的父节点是它本身)。
p[x] = find(p[x]);
:如果 x
不是祖宗节点,那么递归调用 find
函数查找 p[x]
的祖宗节点,并将 x
的父节点直接设置为这个祖宗节点。这样,在后续查找 x
或其子孙节点的祖宗节点时,就可以直接找到,避免了重复查找,实现了路径压缩。
连通块中点的数量
给定一个包含 n个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b
,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b
,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a
,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b
,Q1 a b
或 Q2 a
中的一种。
输出格式
对于每个询问指令 Q1 a b
,如果 a 和 b 在同一个连通块中,则输出 Yes
,否则输出 No
。
对于每个询问指令 Q2 a
,输出一个整数表示点 a 所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例:
1 2 3 4 5 6
| 5 5 C 1 2 Q1 1 2 Q2 1 C 2 5 Q2 5
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <iostream> using namespace std;
const int N = 100010;
int n, m; int p[N], cnt[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
int main() { scanf("%d%d", &n, &m); for (int i = 0; i <= n; i ++ ) { p[i] = i; cnt[i] = 1; } while (m -- ) { char op[5]; int a, b; scanf("%s", op); if (op[0] == 'C') { scanf("%d%d", &a, &b); if (find(a) == find(b)) continue; cnt[find(b)] += cnt[find(a)]; p[find(a)] = find(b); } else if (op[1] == '1') { scanf("%d%d", &a, &b); if (find(a) == find(b)) puts("Yes"); else puts("No"); } else { scanf("%d", &a); printf("%d\n", cnt[find(a)]); } } return 0; }
|
食物链
动物王国中有三类动物 A,B,C这三类动物的食物链构成了有趣的环形。
A 吃 B,B吃 C,C吃A。
现有 N 个动物,以 1∼N 编号。
每个动物都是 A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 N 个动物所构成的食物链关系进行描述:
第一种说法是 1 X Y
,表示 X 和 Y 是同类。
第二种说法是 2 X Y
,表示 X 吃 Y。
此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话;
- 当前的话中 X 或 Y 比 N 大,就是假话;
- 当前的话表示 X 吃 X,就是假话。
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
输入格式
第一行是两个整数 N 和 K,以一个空格分隔。
以下 K 行每行是三个正整数 D,X,Y两数之间用一个空格隔开,其中 DD 表示说法的种类。
若 D=1,则表示 X 和 Y 是同类。
若 D=2,则表示 X 吃 Y。
输出格式
只有一个整数,表示假话的数目。
数据范围
1 ≤ N ≤ 50000,
0 ≤ K ≤ 100000
输入样例:
1 2 3 4 5 6 7 8
| 100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
|
输出样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> using namespace std; const int N = 50010;
int n, m; int p[N], d[N];
int find(int x) { if(p[x] != x) { int t = find(p[x]); d[x] += d[p[x]]; p[x] = t; } return p[x]; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) p[i] = i; int res = 0; while (m -- ) { int t, x, y; scanf("%d%d%d", &t, &x, &y); if (x > n || y > n) res ++ ; else { int px = find(x), py = find(y); if(t == 1) { if (px == py && (d[x] - d[y]) % 3) res ++ ; else if (px != py) { p[px] = py; d[px] = d[y] - d[x]; } } else { if (px == py && (d[x] - d[y] - 1) % 3) res ++ ; else if(px != py) { p[px] = p[y]; d[px] = d[y] + 1 - d[x]; } } } } printf("%d\n", res); return 0; }
|