介绍

  摘自罗勇军,郭卫斌的《算法竞赛入门到进阶》上的说明:
  并查集(Disjoint Set)是一种非常精巧而且食用的数据结构,它主要用于处理一些不相交集合的合并问题。经典的例子有连通子图、最小生成树Kruskal算法和最近公共祖先(Lowser Common Ancestors, LCA)等。在欧拉路上也常常会用到并查集。
  并查集:将编号分别为1-n的n个对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合。在这个集合中,并查集的操作有初始化,合并和查找。
  以下有模板和习题,习题都是来自于该书的,大概分为并查集模板题(1,2,3,9),带权并查集(4,5,6,7,8),并查集的应用(10,11,12,13)。
  这里根据我认为的难度做了标记"<?>",如果有十分相似的题难度不一样,是因为如果把难度高的同类题做完了自然就感觉难度下降了。

文章目录

  • 介绍
  • 模板
      • 初级的模板
    • 模板题:HDU - 1213 How Many Tables <1>
      • 合并的优化
      • 查询的优化——路径压缩
  • 习题
    • 1.POJ - 2524 Ubiquitous Religions <1>
    • 2.POJ - 1611 The Suspects <1>
    • 3.POJ - 2236 Wireless Network <1>
    • 4.POJ - 1703 Find them, Catch them <3>
    • 5.POJ - 2492 A Bug's Life <2>
    • 6.POJ - 1182 食物链<2>
    • 7.POJ - 1988 Cube Stacking <4>
    • 8.HDU - 3635 Dragon Balls <3>
    • 9.HDU - 1856 More is better <1>
    • 10.HDU - 1272 小希的迷宫 <2>
    • 11.HDU - 1325 Is It A Tree? <2>
    • 12 HDU - 1198 Farm Irrigation <2>
    • 13.HDU - 6109 数据分割 <2>
  • 结语

模板

初级的模板
int s[maxn];
void init_set() {//初始化for (int i = 1; i <= maxn; i++)s[i] = i;
}
int find_set(int x) {//查找return x == s[x] ? x : find_set(s[x]);
}
void union_set(int x, int y) {//合并x = find_set(x);y = find_set(y);if (x != y)s[x] = s[y];
}

评价:由于这些操作搜索深度是树的长度,时间复杂度为O(n)。

模板题:HDU - 1213 How Many Tables <1>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213

#include<cstdio>
using namespace std;
const int maxn = 1000 + 5;
int s[maxn];
void init_set(int n) {for (int i = 1; i <= n; i++)s[i] = i;
}
int find_set(int x) {return x == s[x] ? x : find_set(s[x]);
}
void union_set(int x, int y) {x = find_set(x);y = find_set(y);if (x != y)s[x] = s[y];
}
int main(void) {int t, n, m, a, b;scanf("%d", &t);while (t--) {scanf("%d %d", &n, &m);init_set(n);for (int i = 1; i <= m; i++) {scanf("%d %d", &a, &b);union_set(a, b);}int cnt = 0;for (int i = 1; i <= n; i++)if (s[i] == i)cnt++;printf("%d\n", cnt);}return 0;
}
合并的优化

合并操作时特意将高度较小的集合并到较大的集合上,能减少树的高度,时间复杂度为θ(nlogn) (证明未知)。

int s[maxn],height[maxn];
void init_set(int n) {for (int i = 1; i <= n; i++){s[i] = i; height[i]=0;}
}
void union_set(int x, int y) {x = find_set(x);y = find_set(y);if (height[x] == height[y]) {height[x]++; s[y] = x;//还有一种方法是点数小的集合并到大的集合}else {if (height[x] < height[y])//高度小的集合并到大的集合s[x] = y;elses[y] = x;}
}
查询的优化——路径压缩

将所有后代结点直接指向祖先结点即根结点,可以达到O(1)的查询效果。
递归形式:

int find_set(int x) {return x == s[x] ? x : s[x] = find_set(s[x]);
}

非递归形式:

int find_set(int x) {int r = x, i = x, j;while (s[r] != r)r = s[r];//先确立根结点while (i != j) {//把路径上的结点依次指向根节点j = s[i];s[i] = r;i = j;}
}

习题

1.POJ - 2524 Ubiquitous Religions <1>

题目链接:https://vjudge.net/problem/POJ-2524

#include<cstdio>
using namespace std;
const int maxn = 50000 + 5;
int s[maxn], n, m, a, b, kase, cnt;
void init_set(int n) {cnt = 0;for (int i = 1; i <= n; i++)s[i] = i;
}
int find_set(int x) {return x == s[x] ? x : s[x] = find_set(s[x]);
}
void union_set(int x, int y) {x = find_set(x);y = find_set(y);if (x != y)s[x] = y;
}
int main(void) {while (~scanf("%d %d", &n, &m) && n) {init_set(n);for (int i = 1; i <= m; i++) {scanf("%d %d", &a, &b);union_set(a, b);}for (int i = 1; i <= n; i++)if (s[i] == i)cnt++;printf("Case %d: %d\n", ++kase, cnt);}return 0;
}
2.POJ - 1611 The Suspects <1>

题目链接:https://vjudge.net/problem/POJ-1611

#include<cstdio>
using namespace std;
const int maxn = 30000 + 5;
int s[maxn], n, m, k, a, cnt[maxn];
void init_set(int n) {for (int i = 0; i < n; i++) {s[i] = i; cnt[i] = 1;}
}
int find_set(int x) {return x == s[x] ? x : s[x] = find_set(s[x]);
}
void union_set(int x, int y) {x = find_set(x);y = find_set(y);if (x != y) {if (!x) {s[y] = x; cnt[x] += cnt[y]; cnt[y] = 0;}else {s[x] = y; cnt[y] += cnt[x]; cnt[x] = 0;}}
}
int main(void) {while (~scanf("%d %d", &n, &m) && n) {init_set(n);for (int i = 1; i <= m; i++) {scanf("%d", &k);int root = -1;for (int i = 0; i < k; i++) {scanf("%d", &a);if (root < 0)root = a;union_set(root, a);}}printf("%d\n", cnt[0]);}return 0;
}
3.POJ - 2236 Wireless Network <1>

题目链接:https://vjudge.net/problem/POJ-2236

分析:又一个模板题,数据宽松,直接暴力也不卡时间…

#include<cmath>
#include<iostream>
using namespace std;
const int maxn = 1000 + 5;
int s[maxn], x[maxn], y[maxn], ok[maxn], n, d, p, q, sum;
void init(int n) {for (int i = 1; i <= n; i++) s[i] = i;
}
int fa(int x) {return x == s[x] ? x : s[x] = fa(s[x]);
}
void bing(int x, int y) {x = fa(x);y = fa(y);if (x != y)s[x] = s[y];
}
int main(void) {cin >> n >> d;init(n);for (int i = 1; i <= n; i++) {cin >> x[i] >> y[i];}char op;while (cin >> op) {cin >> p;if (op == 'O') {int cnt = 0; for (int i = 1; i <= n; i++) {if (!ok[i])continue;double dis = sqrt(pow(1.0 * (x[p] - x[i]), 2.0) + pow(1.0 * (y[p] - y[i]), 2.0));if (dis <= d)bing(p, i);if (cnt == sum)break;}ok[p] = 1; sum++;}else {cin >> q;if (fa(p) == fa(q))cout << "SUCCESS\n";else cout << "FAIL\n";}}return 0;
}
4.POJ - 1703 Find them, Catch them <3>

题目链接:https://vjudge.net/problem/POJ-1703

知识点:关系型并查集
分析:根据题意总共有两个集合,那么可以建立父集的大小为2*n,假想结点x有对立面x+n。对于结点a,b,若a,b为不同类,则认为a+n和b同类,a和b+n同类,然后查找的时候判断4个结点的关系即可得知a,b是否同类。
参考博客:https://blog.csdn.net/ky961221/article/details/53384638

#include<cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int s[2 * maxn], n, m, a, b;
char cmd;
void init_set(int n) {for (int i = 0; i < 2 * n; i++) s[i] = i;
}
int find_set(int x) {return x == s[x] ? x : s[x] = find_set(s[x]);
}
void union_set(int x, int y) {x = find_set(x);y = find_set(y);if (x != y)s[x] = s[y];
}
int main(void) {int T; scanf("%d", &T);while(T--){scanf("%d %d", &n, &m);init_set(n);for (int i = 1; i <= m; i++) {getchar(); scanf("%c %d %d", &cmd, &a, &b);if (cmd == 'D') {union_set(a + n, b);union_set(a, b + n);}else {//集合相同,要么直接在同一集合,要么对立面在同一集合if (find_set(a) == find_set(b) || find_set(a + n) == find_set(b + n))printf("In the same gang.\n");else if (find_set(a) == find_set(b + n) || find_set(a + n) == find_set(b))printf("In different gangs.\n");elseprintf("Not sure yet.\n");}}}return 0;
}
5.POJ - 2492 A Bug’s Life <2>

题目链接:https://vjudge.net/problem/POJ-2492
分析:和上一题一模一样。

#include<cmath>
#include<cstdio>
using namespace std;
const int maxn = 2000 + 5;
int s[2 * maxn], T, n, m, x, y;
void init(int n) {for (int i = 1; i <= 2 * n; i++)s[i] = i;
}
int fa(int x) {return x == s[x] ? x : s[x] = fa(s[x]);
}
void bing(int x, int y) {x = fa(x);y = fa(y);if (x != y)s[x] = s[y];
}
int main(void) {scanf("%d", &T);for (int kase = 1; kase <= T; kase++) {scanf("%d %d", &n, &m);init(n); int flag = 0;for (int i = 1; i <= m; i++) {scanf("%d %d", &x, &y);if (fa(x) == fa(y)||fa(x+n)==fa(y+n))flag = 1;bing(x + n, y);bing(x, y + n);}if (kase != 1)printf("\n");printf("Scenario #%d:\n", kase);if (flag)printf("Suspicious bugs found!\n");else printf("No suspicious bugs found!\n");}return 0;
}
6.POJ - 1182 食物链<2>

题目链接:https://vjudge.net/problem/POJ-1182
分析:(带权)也为关系型并查集,该题有三类集合,同样可以用第4题的办法,a与b同类,则吃a的(即a+n)与吃b的(即b+n)同类,被a吃的(即a+2*n)与被b吃的(即b+2n)同类。若a吃b,则a于b+n同类,a+n与b+2*n同类,a+2n与b同类。总之就是a为一类,吃a的为一类,被a吃的为一类。

#include<cstdio>
using namespace std;
const int maxn = 5e5 + 5;
int s[3 * maxn], n, k, d, x, y, ans;
void init_set(int n) {for (int i = 1; i <= 3 * n; i++) s[i] = i;
}
int fa(int x) {return x == s[x] ? x : s[x] = fa(s[x]);
}
void union_set(int x, int y) {x = fa(x);y = fa(y);if (x != y)s[x] = s[y];
}
int main(void) {scanf("%d %d", &n, &k);init_set(n);while (k--) {scanf("%d %d %d", &d, &x, &y);if (x > n || y > n) {ans++; continue;}if (d == 1) {//X吃Y 或 X被Y吃 THEN falseif (fa(x) == fa(y + n) || fa(x) == fa(y + 2 * n)) {ans++; continue;}union_set(x, y);//y的同类union_set(x + n, y + n);//吃y的为一类union_set(x + 2 * n, y + 2 * n);//被y吃的为一类}else {if (x == y)ans++;else {//X同Y 或 Y吃X THEN false       if (fa(x) == fa(y) || fa(y) == fa(x + n)) {ans++; continue;}union_set(x, y + n);//吃y的为一类union_set(x + n, y + 2 * n);//被y吃的为一类union_set(x + 2 * n, y);//y的同类}}}printf("%d\n", ans);return 0;
}

总结:这类题可以根据题意来分出k个关系,若元素最多为n,则元素a与a,a+n,a+2*n,… ,a+(k-1)*n分别有着对应的关系,再通过这些关系解决问题。

7.POJ - 1988 Cube Stacking <4>

题目链接:https://vjudge.net/problem/POJ-1988

新知识点,通过其他博客得知这类也为带权并查集。主要的要点是利用好了递归来更新数据,要做到在路径压缩的同时还使得数据可以保证正确的动态变化真的好神奇…
参考博客:https://blog.csdn.net/qq_43750980/article/details/98500722
代码说明: 增加数组d和size,d表示该点到根结点要走几步,size表示集合一共有多少个结点,则答案为size-d-1(除去自身)。
重点:更新距离=原距离(到原祖先结点距离)+原祖先到新祖先的距离

#include<cmath>
#include<cstdio>
using namespace std;
const int maxn = 3e5 + 5;
int f[maxn], d[maxn], size[maxn], P, x, y;
void init() {for (int i = 1; i < maxn; i++) {f[i] = i; size[i] = 1; d[i] = 0;}
}
int find(int x) {if (x == f[x])return x;int fa = find(f[x]);//必须先进入下一层更新父节点的d[x]d[x] += d[f[x]];return f[x] = fa;//父节点更新的是父节点的父节点因此f[x]仍可用
}
void bing(int x, int y) {x = find(x);y = find(y);f[y] = x;d[y] = size[x];size[x] += size[y];
}
int main(void) {char op;scanf("%d", &P);init();while (P--) {getchar();scanf("%c %d", &op, &x);if (op == 'M') {scanf("%d", &y); bing(x, y);}else  printf("%d\n", size[find(x)] - d[x] - 1);}return 0;
}
8.HDU - 3635 Dragon Balls <3>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3635

分析:上一题的套路,每次新转移发生时,原根结点必定是第一次转移,而子节点都随着根节点的转移而转移,因此也可以用递归的方式来更新移动次数。

#include<cstdio>
using namespace std;
const int maxn = 10000 + 5;
int T, N, M, x, y, f[maxn], times[maxn], cnt[maxn];
void init(int N) {for (int i = 1; i <= N; i++) {f[i] = i; cnt[i] = 1; times[i] = 0;}
}
int find(int x) {if (x == f[x])return x;int fa = find(f[x]);times[x] += times[f[x]];return f[x] = fa;
}
void bing(int x, int y) {x = find(x);y = find(y);if (x != y) {//将x转移到yf[x] = y;cnt[y] += cnt[x];times[x]++;}
}
int main(void) {char op;scanf("%d", &T);for (int kase = 1; kase <= T; kase++) {scanf("%d %d", &N, &M);init(N);printf("Case %d:\n", kase);while (M--) {getchar();scanf("%c %d", &op, &x);if (op == 'T') {scanf("%d", &y); bing(x, y);}else {int id = find(x);printf("%d %d %d\n", id, cnt[id], times[x]);}}}return 0;
}
9.HDU - 1856 More is better <1>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1856
分析:又来模板题了,注意答案可能为1,即输入为0。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10000000 + 5;
int T, x, y, f[maxn], cnt[maxn], ans;
void init(int N) {ans = 1;for (int i = 1; i < maxn; i++) {f[i] = i; cnt[i] = 1;}
}
int find(int x) {if (x == f[x])return x;return f[x] = find(f[x]);
}
void bing(int x, int y) {x = find(x);y = find(y);if (x != y) {f[x] = y;cnt[y] += cnt[x];ans = max(ans, cnt[y]);}
}
int main(void) {while (~scanf("%d", &T)) {init(T);while (T--) {scanf("%d %d", &x, &y);bing(x, y);}printf("%d\n", ans);}return 0;
}
10.HDU - 1272 小希的迷宫 <2>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1272
分析:该题的并查集作用是判断是否输入完之后只有一个集合。n个结点用n-1条边连接起来,如果任意再连上两个结点必将构成回路,不合题意,根据这点判断迷宫是否合法。

#include<set>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100000 + 5;
set<int>vised;
int T, x, y, f[maxn];
void init() {vised.clear();for (int i = 1; i < maxn; i++) f[i] = i;
}
int find(int x) {if (x == f[x])return x;return f[x] = find(f[x]);
}
void bing(int x, int y) {vised.insert(x);vised.insert(y);x = find(x);y = find(y);if (x != y) f[x] = y;
}
int main(void) {while (~scanf("%d %d", &x, &y) && x != -1) {if (x == 0) { printf("Yes\n"); continue; }init(); bing(x, y);int num = 0, fa = -1, cnt = 1;while (~scanf("%d %d", &x, &y) && x) {bing(x, y); cnt++;}for (set<int>::iterator it = vised.begin(); it != vised.end(); ++it) {if (*it == f[*it])num++;}if (num!=1)printf("No\n");else {;if (cnt != vised.size() - 1)printf("No\n");else printf("Yes\n");}}return 0;
}
11.HDU - 1325 Is It A Tree? <2>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1325
分析:乍看一下,似乎和上题一模一样,但是其实不对。该题是有方向的,而且需要记录入度,还有结束标志是输入为负数而不是-1 -1。
知识点:只有一个结点入度为0,其它结点入度均为1的图是树。

#include<set>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100000 + 5;
set<int>vised;
int T, x, y, f[maxn], in[maxn], kase;
void init() {vised.clear();for (int i = 1; i < maxn; i++) {f[i] = i; in[i] = 0;}
}
int find(int x) {if (x == f[x])return x;return f[x] = find(f[x]);
}
void bing(int x, int y) {vised.insert(x);vised.insert(y);x = find(x);y = find(y);if (x != y) f[x] = y;
}
int main(void) {while (~scanf("%d %d", &x, &y) && x >= 0) {printf("Case %d is ", ++kase);if (x == 0) { printf("a tree.\n"); continue; }init(); bing(x, y); in[y]++;int num = 0, fa = -1, num2 = 0, num3 = 0;while (~scanf("%d %d", &x, &y) && x) {bing(x, y); in[y]++;}for (set<int>::iterator it = vised.begin(); it != vised.end(); ++it) {if (*it == f[*it])num++;if (in[*it] == 1)num2++;if (in[*it] == 0)num3++;}if (num != 1 || num3 != 1 || num2 != vised.size() - 1)printf("not a tree.\n");else printf("a tree.\n");}return 0;
}
12 HDU - 1198 Farm Irrigation <2>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1198
分析:并查集的应用,其实DFS,BFS也差不多,我猜想代码量都不会差很大。存好各种块的上下左右是否可以连通就可以了。

#include<cstdio>
using namespace std;
const int maxn = 256;
int T, u[maxn], d[maxn], l[maxn], r[maxn], f[2505], m, n;
char g[55][55];
void init() {for (int i = 0; i < 2505; i++)f[i] = i;
}
int find(int x) {return x == f[x] ? x : f[x] = find(f[x]);
}
void bing(int x, int y) {x = find(x);y = find(y);if (x != y) f[x] = y;
}
void solve() {for (int i = 0; i < m; i++)for (int j = 0; j < n; j++) {if (u[g[i][j]] && i-1 >=0  && d[g[i - 1][j]])bing(i * n + j, (i - 1) * n + j);if (d[g[i][j]] && i + 1 < m && u[g[i + 1][j]])bing(i * n + j, (i + 1) * n + j);if (l[g[i][j]] && j - 1 >= 0 && r[g[i][j - 1]])bing(i * n + j, i * n + j - 1);if (r[g[i][j]] && j + 1 < n && l[g[i][j + 1]])bing(i * n + j, i * n + j + 1);}
}
int main(void) {u['A'] = u['B'] = u['E'] = u['G'] = u['H'] = u['J'] = u['K'] = 1;d['C'] = d['D'] = d['E'] = d['H'] = d['I'] = d['J'] = d['K'] = 1;l['A'] = l['C'] = l['F'] = l['G'] = l['H'] = l['I'] = l['K'] = 1;r['B'] = r['D'] = r['F'] = r['G'] = r['I'] = r['J'] = r['K'] = 1;while (~scanf("%d %d", &m, &n) && m > 0) {init();for (int i = 0; i < m; i++) {getchar();for (int j = 0; j < n; j++) {scanf("%c", &g[i][j]);}}solve();int cnt = 0;for (int i = 0; i < m; i++)for (int j = 0; j < n; j++) if (f[i * n + j] == i * n + j)cnt++;printf("%d\n", cnt);}return 0;
}
13.HDU - 6109 数据分割 <2>

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6109
分析:并查集存同类,set存异类,暴力初始化,时间也过得去,数据不强。
解释一下题意:输入L行,本来是有k组数据被分隔符分开了,但是分隔符被小w删去看不出来数据分组,但已知每组数据的最后一个约束条件会弄假该组数据的逻辑,也就是一旦遇到某约束条件使得逻辑为假则出现独立的一组数据,如果到最后也没有遇到,则不为一组数据。

#include<cstdio>
#include<vector>
#include<set>
using namespace std;
const int maxn = 100000 + 5;
int L, i, j, e, s[maxn], cnt;
vector<int>ans;
set<int>dif[maxn];
void init() {for (int i = 0; i < maxn; i++) {s[i] = i; dif[i].clear();}
}
int find(int x) {return x == s[x] ? x : s[x] = find(s[x]);
}
void merge(int x, int y) {x = find(x);y = find(y);if (x != y) s[x] = y;for (set<int>::iterator it = dif[x].begin(); it != dif[x].end(); ++it)dif[y].insert(*it);
}
int main(void) {scanf("%d", &L);init();while (L--) {scanf("%d %d %d", &i, &j, &e);cnt++;if (e) {if (dif[find(i)].count(find(j)) || dif[find(j)].count(find(i))) {ans.push_back(cnt);cnt = 0; init();continue;}merge(i, j);}else {if (find(i) == find(j)) {ans.push_back(cnt);cnt = 0; init();continue;}dif[find(i)].insert(find(j));dif[find(j)].insert(find(i));}}printf("%d\n", ans.size());for (int i = 0; i < ans.size(); i++)printf("%d\n", ans[i]);return 0;
}

结语

  差不多了,本篇到此为止,这里记录的大都是以并查集为主的题,很多算法是以并查集为辅的,还有很多地方需要探索啊,这只是基础而已。书上剩余一个题hdu2586需要用到Tarjan,暂时不会,习题中有7个是和kuangbin专题重合了,还有7个是没有的,估计写了也不是放到一篇文章了,而且应该也有的是水题没必要记录了,慢慢来吧,留到以后复习来写也不错。

并查集入门+初级专题训练相关推荐

  1. 并查集算法总结专题训练

    并查集算法总结&专题训练 1.概述 2.模板 3.例题 1.入门题: 2.与别的算法结合: 3.考思维的题: 4.二维转一维: 5.扩展域并查集&边带权并查集: 4.总结 1.概述 并 ...

  2. The Suspects(并查集入门)

    题目:http://www.fjutacm.com/Problem.jsp?pid=2021 题意大概就是输入n,m,分别代表总共n个人,m组,每组输入k,后面再输入k个人表示是一组的,0号是嫌疑者, ...

  3. 并查集入门三连:HDU1213 POJ1611 POJ2236

    HDU1213 http://acm.hdu.edu.cn/showproblem.php?pid=1213 问题描述 今天是伊格纳修斯的生日.他邀请了很多朋友.现在是晚餐时间.伊格纳修斯想知道他至少 ...

  4. HDOJ 畅通工程 1232(并查集入门)

    畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  5. [kuangbin带你飞]专题五 并查集 题解+总结

    kuangbin带你飞:点击进入新世界 总结: 本人算是初学者中的初学者,欢迎交流~ 并查集的接触过的不多,大概只有普通并查集,带权并查集,种族并查集,传说中的可持续化并查集只是听说过还没有接触,不过 ...

  6. 并查集——亲戚(洛谷 P1551)

    题目选自洛谷P1551 并查集入门题目,简单有趣的例子了解并查集的使用 主要分为三个操作: 1)初始化 没有什么说的,就是用下标当做标号. 2)合并操作 如果两个关系的"老大"不一 ...

  7. P1551 亲戚 并查集

    P1551 亲戚 题目背景 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系. 题目描述 规定:x和y是亲戚,y和z是亲戚,那么 ...

  8. P1551 亲戚(并查集)

    亲戚 题目背景 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系. 题目描述 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲 ...

  9. 并查集专题练习:好朋友(未完待续)

    有空再把题目补上 输入样例1 4 2 1 4 2 3 样例输出1 2 输入样例2 7 5 1 2 2 3 3 1 1 4 5 6 输出样例2 3 解题思路: 1. 这题放在并查集的专题后面,有查找也有 ...

最新文章

  1. QR分解原理与C实现(一般实矩阵)
  2. 你知道数据中心宕机的真正成本吗?
  3. 怎么学python-如何系统的学习python?
  4. IP地址修改后ORACLE不能使用问题
  5. 关于vue项目中输入框无法正常输入的问题
  6. CTF-Web-常用伪协议用法:
  7. html表单输入的文本框的值,网页设计关于表单输入框的技巧代码
  8. Linux shell编程学习总结
  9. 17岁少年攻击航司系统获刑4年!!!
  10. 【第五课】LNMP环境的入门
  11. javweb音乐网站_音乐网站javaweb源码
  12. 安全测试-Drozer安全测试框架实践记录篇
  13. intouch sqlserver_intouch 连接modbus 。转存数据库方法
  14. chrome中验证码图片不显示的解决办法
  15. java makefile jar包_makefile PRODUCT_BOOT_JARS 处理流程及实例
  16. 零基础学平面设计是自学好还是报班好?
  17. 在移动开发快捷推广方式
  18. 名帖301 刘墉 行书《自作诗卷》
  19. 网页的首屏标准你了解多少?
  20. 毕业后距离就这样慢慢拉开的

热门文章

  1. Java exception was raised during method invocation
  2. 一图带你了解全球疫情爆发背后的隐藏机会
  3. 北京奥林匹克公园三维场景网络发布应用
  4. 图像处理——分水岭算法
  5. gtx660 linux驱动下载,Ubuntu 16.04安装GTX660显卡驱动——解决谷歌浏览器卡死桌面的问题...
  6. java-poi实现生成word
  7. DCloud之APP离线SDK升级步骤(3.5.3升至最新版3.6.7.81556_20221018)
  8. 车牌号归属地查询,免费API
  9. flink报错:The types of the interface org.apache.flink.util.OutputTag could not be inferred.
  10. 二十五岁零基础转行做软件测试怎么样?