①合并两个集合
②查询某个元素的祖宗节点
查找
通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。
在这样的思想下,并查集的查找算法诞生了。
路径压缩
这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我的祖先是谁与我父亲是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。把在路径上的每个节点都直接连接到根上,这就是路径压缩。
合并
宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。
我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。
① 记录每个集合的大小 -》 绑定到根节点
② 记录每个点到根节点的距离 -》 绑定到每个点身上
输入样例:
3 5 1 1 D 1 1 R 1 2 D 2 1 R 2 2 D输出样例:
4
问题等价于:在一次次连边的过程中,什么时候第一次连成一个环
可以将其看成是图论中的点跟边,出现环的时候,等价于,两个点在连通之前就已经在同一个连通块当中
- #include
- #include
- #include
-
- using namespace std;
-
- const int N = 400010;
-
- int n, m;
- int p[N];
-
- // 坐标映射
- int get(int x, int y)
- {
- return x * n + y;
- }
-
- int find(int x)
- {
- if(p[x] != x) p[x] = find(p[x]);
- return p[x];
- }
-
- int main()
- {
- cin >> n >> m;
-
- for(int i = 1; i <= n * n; i ++ ) p[i] = i;
-
- int res = 0;
- for(int i = 1; i <= m; i ++ )
- {
- int x, y;
- char d;
- cin >> x >> y >> d;
- x --, y -- ;
- int a = get(x, y);
- int b;
- if(d == 'D') b = get(x + 1, y);
- else b = get(x, y + 1);
-
- int pa = find(a), pb = find(b);
- if(pa == pb)
- {
- res = i;
- break;
- }
- p[pa] = pb;
- }
-
- if(!res) puts("draw");
- else cout << res << endl;
-
- return 0;
- }
-
输入样例:
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2输出样例:
1
把每一个联通块看做是一个物品,然后就变成了一个01 背包问题了。
- #include
- #include
- #include
-
- using namespace std;
-
- const int N = 10010;
-
- int n, m, vol;
- int v[N], w[N];
- int p[N];
- int f[N];
-
- int find(int x)
- {
- if(p[x] != x) p[x] = find(p[x]);
- return p[x];
- }
-
- int main()
- {
- cin >> n >> m >> vol;
-
- for(int i = 1; i <= n; i ++ ) p[i] = i;
-
- for(int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
-
- while(m -- )
- {
- int a, b;
- cin >> a >> b;
- int pa = find(a), pb = find(b);
- if(pa != pb)
- {
- v[pb] += v[pa];
- w[pb] += w[pa];
- p[pa] = p[pb];
- }
- }
-
- // 01背包问题
- for(int i = 1; i <= n; i ++ )
- if(p[i] == i)
- for(int j = vol; j >= v[i]; j -- )
- f[j] = max(f[j], f[j - v[i]] + w[i]);
-
- cout << f[vol] << endl;
-
- return 0;
- }
输入样例:
2 2 1 2 1 1 2 0 2 1 2 1 2 1 1输出样例:
NO YES
思路
1.读入数据并加入离散化数组
2.离散化后去重,并用此数组的个数初始化并查集
3.按照先相等后不等的顺序,先维护所有的相等关系;最后在依次查看每一对不等关系是否出现矛盾坑点
1.一开始的数组大小要开两倍
2.离散化之后注意映射是从0开始还是从1开始
3.并查集初始化有一个小技巧,初始化的数量直接是alls数组离散化去重之后的元素个数(下标从1开始映射)
4.离散化的查询和合并操作都应该在映射的数上进行操作
#include #include #include #include #include using namespace std; const int N = 2000010; int n, m; int p[N]; unordered_map<int, int> S; struct Query { int x, y, e; }query[N]; int get(int x) { if(S.count(x) == 0) S[x] = ++ n; return S[x]; } int find(int x) { if(p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { int T; cin >> T; while(T -- ) { n = 0; cin >> m; S.clear(); for(int i = 0; i < m; i ++ ) { int x, y, e; scanf("%d%d%d", &x, &y, &e); query[i] = {get(x), get(y), e}; } for(int i = 1; i <= n; i ++ ) p[i] = i; // 合并所有的相等条件 for(int i = 0; i < m; i ++ ) if(query[i].e == 1) { int pa = find(query[i].x), pb = find(query[i].y); p[pa] = p[pb]; } // 检查所有不等条件 bool has_conflict = false; for(int i = 0; i < m; i ++ ) if(query[i].e == 0) { int pa = find(query[i].x), pb = find(query[i].y); if(pa == pb) { has_conflict = true; break; } } if(has_conflict) puts("NO"); else puts("YES"); } return 0; }
输入样例:
4 M 2 3 C 1 2 M 2 4 C 4 2输出样例:
-1 1
① 不问间隔多少战舰 -》 并查集
② 同时维护间隔多少战舰 -》 统一维护当前战舰到排头的距离
③
d[x] 表示 x 到 p[x] 的距离
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- const int N = 30010;
-
- int m;
- int p[N], sizes[N], d[N];
-
- int find(int x)
- {
- if(p[x] != x)
- {
- int root = find(p[x]);
- d[x] += d[p[x]];
- p[x] = root;
- }
- return p[x];
- }
-
- int main()
- {
- cin >> m;
-
- for(int i = 1; i < N; i ++ )
- {
- p[i] = i;
- sizes[i] = 1;
- }
-
- while(m -- )
- {
- char op[2];
- int a, b;
- scanf("%s%d%d", op, &a, &b);
- if(op[0] == 'M')
- {
- int pa = find(a), pb = find(b);
- if (pa != pb) { // 新加的,不在一个集合中才合并!!!
- d[pa] = sizes[pb]; // pa 到 pb 的距离为 pb 中节点的个数
- sizes[pb] += sizes[pa]; // 更新 pb 中节点的个数
- p[pa] = pb; // 将 pa 合并到 pb 中
- }
- }
- else
- {
- int pa = find(a), pb = find(b);
- if(pa != pb) puts("-1");
- else printf("%d\n", max(0, abs(d[a] - d[b]) - 1));
- }
- }
- return 0;
- }
输入样例:
10 5 1 2 even 3 4 odd 5 6 even 1 6 even 7 10 odd输出样例:
3
来源:《算法竞赛进阶指南》, POJ1733 , kuangbin专题
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- const int N = 20010;
-
- int n, m;
- int p[N], d[N];
- unordered_map<int, int> S;
-
- int get(int x)
- {
- if (S.count(x) == 0) S[x] = ++ n;
- return S[x];
- }
-
- int find(int x)
- {
- if (p[x] != x)
- {
- int root = find(p[x]);
- d[x] += d[p[x]];
- p[x] = root;
- }
- return p[x];
- }
-
- int main()
- {
- cin >> n >> m;
- n = 0;
-
- for (int i = 0; i < N; i ++ ) p[i] = i;
-
- int res = m;
- for (int i = 1; i <= m; i ++ )
- {
- int a, b;
- string type;
- cin >> a >> b >> type;
- a = get(a - 1), b = get(b);
-
- int t = 0;
- if (type == "odd") t = 1;
-
- int pa = find(a), pb = find(b);
- if (pa == pb)
- {
- if (((d[a] + d[b]) % 2 + 2) % 2 != t)
- {
- res = i - 1;
- break;
- }
- }
- else
- {
- p[pa] = pb;
- d[pa] = d[a] ^ d[b] ^ t;
- }
- }
-
- cout << res << endl;
-
- return 0;
- }