并查集
存个最最基础的路径压缩板子:

int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);} void _merge(int x, int y){fa[_find(x)]=_find(y);}
放几道裸题吧。
A - Wireless Network POJ - 2236
不多说。这道题就是很简单的并查集处理,之后在线查询即可。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; const int maxn = 2e6 + 100; int fa[maxn]; double x[maxn], y[maxn]; inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);} inline void _merge(int x, int y){fa[_find(x)]=_find(y);} double Dis(int a, int b) { return sqrt( (x[a]-x[b])*(x[a]-x[b]) + (y[a]-y[b])*(y[a]-y[b]) ); } int main() { int n; double d; scanf("%d%lf", &n, &d); for (int i = 1; i <= n; ++ i) { fa[i] = i; scanf("%lf%lf", &x[i], &y[i]); } char temp[3]; vector<int> rec; while (~scanf("%s", temp)) { if (temp[0] == 'O') { int num; scanf("%d", &num); for (int i = 0; i < rec.size(); ++ i) if (Dis(rec[i], num) <= d) _merge(rec[i], num); rec.push_back(num); } else if (temp[0] == 'S') { int u, v; scanf("%d%d", &u, &v); if (_find(u) != _find(v)) cout << "FAIL" << endl; else cout << "SUCCESS" << endl; } } return 0; }
这道题也就是计算并查集里有几个人。当然也可以开一个数组在合并时记录。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; const int maxn = 3e4 + 100; int fa[maxn]; int x[maxn], y[maxn]; inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);} inline void _merge(int x, int y){fa[_find(x)]=_find(y);} int main() { int n, m; while (~scanf("%d%d", &n, &m)) { if (!n && !m) break; for (int i = 1; i <= n; ++ i) fa[i] = i; for (int i = 1, num, temp; i <= m; ++ i) { scanf("%d", &num); vector<int> rec; while (num--) { scanf("%d", &temp); rec.push_back(temp+1); } for (int j = 1; j < rec.size(); j ++) _merge(rec[j-1], rec[j]); } int ans = 1; for (int i = 2; i <= n; ++ i) if (_find(i) == _find(1)) ++ans; cout << ans << endl; } return 0; }
C - How Many Tables HDU - 1213
统计有几个集合。

#include <bits/stdc++.h> #include <unordered_map> using namespace std; const int maxn = 1e5+100; int fa[maxn]; inline int _find(int x) {return fa[x]==x?x:fa[x]=_find(fa[x]);} inline void _merge(int x, int y) {fa[_find(x)] = _find(y);} int main() { int T; scanf("%d", &T); while (T--) { int n, m, cnt = 0; scanf("%d%d", &n, &m); unordered_map<int, int> vis; for (int i = 1; i <= n; ++ i) fa[i] = i; for (int i = 1, u, v; i <= m; ++ i) scanf("%d%d", &u, &v), _merge(u, v); for (int i = 1; i <= n; ++ i) if (!vis[_find(i)]) vis[_find(i)] = ++cnt; cout << cnt << endl; } }
总算来到了带权并查集!(或者说叫关系并查集或者拓展域并查集)
我记得大一学的时候觉得这个非常的难。但是现在我题量上来之后,理解能力也变强了好多。(that's why i love acm.
这个向量法,真的很神。
把一个难以理解的东西转化为那么具象,如果不懂只需要画图就好了。
关于合并:
因为我的代码是 fa[fy] = fx; 所以最后 fa[fy] 代表的就是 fy 到 fx的 关系。
那么根据向量法 fy->fx = -fx->x + x->y + y->fy;
fa[x] : x -> fx
fa[y] : y -> fy
d : x ->y
所以 fa[fy] = -fa[x] + d + fa[y];
附上几道例题:
D - How Many Answers Are Wrong HDU - 3038
很基础带权并查集,但是需要注意的是,题目给出的都是闭区间,所以你需要把他变成左开右闭。
不然的话两个闭区间就会共用一个点。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; const int maxn = 3e5 + 100; int fa[maxn]; int sum[maxn]; int x[maxn], y[maxn]; inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); sum[x] += sum[temp]; return fa[x]; } int main() { int n,m; while (~scanf("%d%d",&n,&m)){ for (int i = 0; i <= n; i++) fa[i] = i, sum[i] = 0; int ans = 0; while (m--){ int a, b, v; scanf("%d%d%d", &a, &b, &v); a--; int roota = _find(a); int rootb = _find(b); if (roota == rootb){ if(sum[a]-sum[b] != v) ans++; } else { fa[roota] = rootb; sum[roota] = -sum[a]+sum[b]+v; } } printf("%d\n", ans); } return 0; }
这是一道很经典的带权并查集。注意的是在做关系相减的时候需要加上模数,不然可能会出现负数的情况。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; const int maxn = 3e5 + 100; int fa[maxn]; int rela[maxn]; int x[maxn], y[maxn]; inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); rela[x] = (rela[x] + rela[temp]) % 3; return fa[x]; } int main() { int n,m; scanf("%d%d",&n,&m); for (int i = 0; i <= n; i++) fa[i] = i, rela[i] = 0; int ans = 0; while (m--){ int x, y, v; scanf("%d%d%d", &v, &x, &y); if (x > n || y > n || (v == 2 && x == y)) { ++ ans; continue; } int fx = _find(x), fy = _find(y); if (fx == fy) { if ((rela[x] - rela[y] + 3) % 3 != v-1) ++ ans; } else { fa[fy] = fx; rela[fy] = (rela[x]-rela[y]-v+1+3) % 3; } } printf("%d\n", ans); return 0; }
这道题跟上面差不多。需要注意的是他的取数范围有1e9,而询问只有5e3所以我们需要用到离散化的技巧。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <map> using namespace std; const int maxn = 3e5 + 100; int fa[maxn]; int rela[maxn]; int x[maxn], y[maxn]; inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); rela[x] = (rela[x] + rela[temp]) % 2; return fa[x]; } int main() { int len, Q; scanf("%d%d", &len, &Q); for (int i = 1; i <= 5000; ++ i) fa[i] = i, rela[i] = 0; int cnt = 0; map<int, int> mp; if (Q == 0) cout << 0 << endl; for (int i = 1; i <= Q; i++) { int L, R; scanf("%d%d", &L, &R); char temp[5]; scanf("%s", temp); int val = strcmp(temp, "odd")+1; //cout << val << endl; L--; if (!mp[L]) mp[L] = ++cnt; if (!mp[R]) mp[R] = ++cnt; int fL = _find(mp[L]), fR = _find(mp[R]); //cout << fL << " " << fR << " " << mp[L] << " " << mp[R] << endl; if (fL == fR) { //cout << -rela[mp[L]] << " " << rela[mp[R]] << endl; if ((-rela[mp[L]]+rela[mp[R]]+2)%2 != val){ cout << i - 1 << endl; break; } } else { rela[fR] = (-rela[mp[L]]+rela[mp[R]]+val+2)%2; fa[fR] = fL; } if (i == Q) cout << Q << endl; } return 0; }
I - Navigation Nightmare POJ - 1984
这道题是离线并查集。说实话我一开始并没有想到维护dx 和 dy (是我菜了
但是想到之后就好写很多了。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <map> using namespace std; const int maxn = 4e4 + 100; int fa[maxn]; int px[maxn], py[maxn]; int n, m; void init() { for (int i = 0; i <= n; ++ i) fa[i] = i, px[i] = py[i] = 0; } inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); px[x] += px[temp]; py[x] += py[temp]; return fa[x]; } char _get[maxn][50]; int main() { scanf("%d%d", &n, &m); init(); getchar(); for(int i = 0; i < m; i++) gets(_get[i]); int k; scanf("%d", &k); int nd1, nd2, ct, cur = 0; while(k--) { int a, b, d; scanf("%d %d %d", &nd1, &nd2, &ct); for(int i = cur; i < ct; i++) { int dx = 0, dy = 0; char dir[2]; sscanf(_get[i], "%d %d %d %s", &a, &b, &d, dir); switch(dir[0]) { case 'E': dx += d; break; case 'W': dx -= d; break; case 'N': dy += d; break; case 'S': dy -= d; break; } int r1 = _find(a); int r2 = _find(b); if(r1 != r2) { fa[r2] = r1; px[r2] = px[a] + dx - px[b]; py[r2] = py[a] + dy - py[b]; } } cur = ct; //这两步很重要,不只是找到r1和r2的根,还更新了x[],py[],同G题 int r1 = _find(nd1); int r2 = _find(nd2); if(r1 != r2) printf("-1\n"); else printf("%d\n", abs(px[nd1]-px[nd2]) + abs(py[nd1]-py[nd2])); } }
这道题较裸。需要注意的是一旦出现不符合就要跳出程序。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <map> using namespace std; const int maxn = 4e4 + 100; int fa[maxn]; int rela[maxn]; int n, m; void init() { for (int i = 0; i <= n; ++ i) fa[i] = i, rela[i] = 0; } inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); rela[x] = (rela[x] + rela[temp]) % 2; return fa[x]; } int main() { int T; scanf("%d", &T); for (int Ti = 1; Ti <= T; ++ Ti) { scanf("%d%d", &n, &m); init(); int flag = 0; for (int i = 1; i <= m; i++) { int x, y; scanf("%d %d", &x, &y); if (flag) continue; int fx = _find(x), fy = _find(y); if (fx == fy) { if (rela[x] == rela[y]) flag = 1; } else { fa[fy] = fx; rela[fy] = (rela[x] - rela[y] + 1) % 2; } } printf("Scenario #%d:\n", Ti); if (flag) printf("Suspicious bugs found!\n"); else printf("No suspicious bugs found!\n"); printf("\n"); } }
这道题就比较tricky的一道题。需要枚举每个人为裁判,之后删除跟他相关的关系。看看是否有矛盾出现。
这里还要统计裁判的个数。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <map> using namespace std; const int maxn = 1e5 + 100; int fa[maxn]; int rela[maxn]; int a[maxn], b[maxn]; char ch[maxn]; int n, m; void init() { for (int i = 0; i <= n; ++ i) fa[i] = i, rela[i] = 0; } inline int _find(int x){ if (fa[x] == x) return x; int temp = fa[x]; fa[x] = _find(fa[x]); rela[x] = (rela[x] + rela[temp] + 3) % 3; return fa[x]; } int unint(int x, int y, int d) { int fx = _find(x), fy = _find(y); if(fx != fy) fa[fx] = fy, rela[fx] = (-rela[x] + rela[y] + d + 3) % 3; else if((rela[x] - rela[y] + 3) % 3 != d) return 1; return 0; } int main() { while(~scanf("%d%d",&n, &m)) { init(); int d; for(int i = 1; i <= m; i++) scanf("%d%c%d", &a[i], &ch[i], &b[i]); int id = 0, ans = 0, cnt = 0, flag; for(int i = 0; i < n; i++) { init(); flag = 0; for(int j = 1; j <= m; j++) { if(i == a[j] || i == b[j]) continue; if(ch[j] == '=') d = 0; if(ch[j] == '>') d = 1; if(ch[j] == '<') d = 2; if(unint(a[j], b[j], d)) { ans = max(ans, j), flag = 1; break; } } if(!flag) id = i, cnt++; } if(cnt == 0) puts("Impossible"); else if(cnt > 1) puts("Can not determine"); else printf("Player %d can be determined to be the judge after %d lines\n", id, ans); } return 0; }
L - Connections in Galaxy War ZOJ - 3261
这道题挺好的。反过来做。先记录下哪些边没有被破坏,之后就一直相连。之后反向查询,之后每遇到destroy就将这条边连上,之后反相输出。

#include<iostream> #include<cstdio> #include<cstring> #include<map> #include<vector> using namespace std; const int maxn=1e4+7; #define pii pair<int, int> map<pii,int> Hash; //hash是用来标记是否边被破坏 struct edge{//记录边的两个端点,以及操作的类型 int s,t,flag; }query[maxn*5]; int num[maxn],w[maxn],fa[maxn]; int n,m,q; void init() { for (int i = 0; i < n; ++i) fa[i] = i, num[i] = w[i]; } int _find(int x){//带权并查集经典做法(最大值) int temp=fa[x]; if(fa[x]!=x){ fa[x]=_find(fa[x]); num[x]=max(num[x],num[temp]); } return fa[x]; } void Union(int x,int y){//合并操作 int fx=_find(x), fy=_find(y); if (fx == fy) return ; if (num[fx]>num[fy] || (num[fx]==num[fy]&&fx<fy)) fa[fy] = fx; else fa[fx] = fy; } int main(){ char opt[10]; int first=1; while(~scanf("%d",&n)) { if(first) first=0; else printf("\n"); for (int i = 0; i < n; ++i) scanf("%d", &w[i]); Hash.clear();//清空 init(); scanf("%d",&m); for(int i=0, u, v;i<m;++i){ scanf("%d%d",&u,&v); if (u > v) swap(u, v); Hash[make_pair(u,v)] = 1; } scanf("%d", &q); for(int i=0,u,v;i<q;++i){ scanf("%s",opt); if (opt[0] == 'q') scanf("%d", &query[i].s), query[i].flag = 0; else{ scanf("%d%d", &u, &v); if (u > v) swap(u, v); query[i].s = u, query[i].t = v; query[i].flag = 1; Hash[make_pair(u,v)] = 0; } } for(auto i : Hash){ if(i.second){ pii temp = i.first; Union(temp.first, temp.second); } } vector<int> v;//用v来存储结果,开数组也行 v.clear(); for(int i = q-1; i >= 0; --i){//反向进行离线处理 if(query[i].flag)//如果该边被破怀过,现在合并,因为是倒着推的 Union(query[i].s,query[i].t); else{ int x = query[i].s, fx = _find(x); if (num[fx] <= w[x]) v.push_back(-1); else v.push_back(fx); } } int len = v.size(); for(int i = len-1; i >= 0; i--) printf("%d\n",v[i]); } return 0; }
其他并查集应用:
就是判断是不是一棵树,需要注意的是0 0 也是算一棵树的。(很像最小生成树的那个判环)

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <set> using namespace std; const int maxn = 100000 + 100; int fa[maxn]; inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);} inline void _merge(int x, int y){fa[_find(x)]=_find(y);} int main() { int u, v; while (~scanf("%d%d", &u, &v)) { if (u == -1 && v == -1) break; if (u == 0 && u == 0) {cout << "Yes" << endl; continue;} int rec = max(u, v); int flag = 0; for (int i = 0; i <= 100000; ++ i) fa[i] = i; fa[u] = v; while (scanf("%d%d", &u, &v)) { if (!u && !v) break; int fx = _find(u), fy = _find(v); if (flag) continue; if (fx == fy) flag = 1; else fa[fy] = fx; rec = max(max(u,v), rec); } set<int> s; for (int i = 1; i <= rec; ++ i) { if (_find(i) == i) continue; s.insert(_find(i)); } if (s.size() > 1) flag = 1; if (flag) cout << "No" << endl; else cout << "Yes" << endl; } return 0; }
这道题相较于上一题多了一个 判断 输入的点是同一个点的情况。

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <set> using namespace std; const int maxn = 100000 + 100; int fa[maxn]; inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);} inline void _merge(int x, int y){fa[_find(x)]=_find(y);} int main() { int u, v; int Ti = 0; while (~scanf("%d%d", &u, &v)) { int rec = max(u, v); int flag = 0; if (u == -1 && v == -1) break; if (u == 0 && v == 0) {cout << "Case " << ++Ti << " is a tree." << endl; continue;} if (u == v) flag = 1; for (int i = 0; i <= 100000; ++ i) fa[i] = i; fa[u] = v; while (scanf("%d%d", &u, &v)) { if (!u && !v) break; int fx = _find(u), fy = _find(v); if (flag) continue; if (fx == fy) flag = 1; else fa[fy] = fx; rec = max(max(u,v), rec); } set<int> s; for (int i = 0; i <= rec; ++ i) { if (_find(i) == i) continue; s.insert(_find(i)); } if (s.size() > 1) flag = 1; if (flag) cout << "Case " << ++Ti << " is not a tree." << endl; else cout << "Case " << ++Ti << " is a tree." << endl; } return 0; }
(虽然我觉得这道题目不是很适合用并查集做。毕竟是有向树。)
这道题并查集的运用显得非常的巧妙。
首先我们要知道这道题的一个贪心策略是,商品一般放在保质期那天卖,才能使收益最大化。
如果有个收益第二大的,但是跟最大的保质期在同一天,那么我们就要将他放在保质期前一天卖。
那么并查集的作用就来了。
fa[t] = t - 1;说明的是第t天已经有商品放在那一天卖了。所以如果还有一个很大的商品要在t天卖,所以应该放在t-1天卖。
直到fa[t] == -1时就代表0 - t天都已经安排上了,已经安排不了其他货物了、

#include <cstring> #include <cstdio> #include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; const int maxn = 2e4+100; const int maxm = 2e7+100; typedef long long ll; const ll inf = 1e17; struct node { int day, val; bool operator <(const node & a) const{ return val > a.val; } }store[maxn]; int fa[maxn]; inline int _find(int x) {return -1==fa[x]?x:fa[x]=_find(fa[x]);} int main() { int n; while (~scanf("%d", &n)) { memset(fa, -1, sizeof(fa)); for (int i = 0; i < n; ++ i) scanf("%d%d", &store[i].val, &store[i].day); sort(store, store+n); ll sum = 0; for (int i = 0; i < n; ++ i) { int t = _find(store[i].day); if (t > 0) { sum += store[i].val; fa[t] = t-1; } } cout << sum << endl; } return 0; }
并查集还挺简单的嘛!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人