一、鄙人的浅薄认知:给你一些具有父子关系的数据,你的任务就是把他们家家谱汇总出来,然后,顺便记录一些年龄啊,几个孩子什么的,最后回答问题就行了。
二、典型例题
1、HDU 1232 畅通工程
http://acm.hdu.edu.cn/showproblem.php?pid=1232
这道题是大多数人的入门题。
题解:有好多村子,现在已经建了一些路了,问还要建多少才能让所有的村子相通。
首先至少ans = n - 1条,如果目前互不认识。然后建立父子关系,不是一个祖宗的给他们建了路了,父子关系成立,他们成为一家子了,ans 自然 - 1。
是一个祖宗的本来就是一家子,不用管。然后一直这样判断直到读入结束。
代码:
1 #include<iostream>
2 #include<cstring>
3 #include<queue>
4 using namespace std;
5 const int N = 1010;
6 int father[N];
7
8 int Find(int x) {
9 if (father[x] == x) return x;
10 return father[x] = Find(father[x]);
11 }
12
13 int main()
14 {
15 int n, m;
16 while (scanf("%d", &n) && n) {
17 scanf("%d", &m);
18 for (int i = 0; i <= n; i++) {
19 father[i] = i;//刚开始自己是自己的父亲
20 }
21 int a, b;
22 int ans = n - 1;
23 for (int i = 0; i < m; i++) {
24 scanf("%d %d", &a, &b);
25 int f1 = Find(a);
26 int f2 = Find(b);
27 if (f1 != f2) {
28 father[f1] = f2;
29 ans--;
30 }
31 }
32 printf("%d\n", ans);
33 }
34 return 0;
35 }
2、HDU 3635 Dragon Balls
http://acm.hdu.edu.cn/showproblem.php?pid=3635
题解:也是简单的并查集,就是比畅通工程多用了一个数组。
孙悟空搞事情,见着美女 T 就把 A 城市的龙珠运到 B 城市,见着美人 Q 就问你 A 龙珠在哪个城市 X ,还有 X 城市有几个龙珠,还有 A 龙珠被糟蹋了几次(运了几回)。
多用一个num[N]记录每个爸爸有几个儿子,用step[N]记录每个龙珠被糟蹋的次数。
代码:
1 #include<cstdio>
2 #include<algorithm>
3 #include<iostream>
4 #include<string>
5 #include<cmath>
6 #include<map>
7 using namespace std;
8 //num记录父节点下的子节点个数 step记录步数
9 int pre[200005],num[200005],step[200005];
10
11 int find(int x){
12 if(pre[x]!=x){
13 int t=pre[x];//改变根节点前用t保存pre[x]
14 pre[x]=find(pre[x]);//更新根节点 ,直到找到父节点
15 step[x]+=step[t];//将下级节点的步数加到他的上级节点步数中
16 }
17 return pre[x];
18 }
19
20 int main()
21 {
22 int T;
23 cin>>T;
24 int n,m,nn=1;
25 int f1,f2;
26 while(T--){
27 printf("Case %d:\n",nn++);//注意位置,一共有T个case
28 scanf("%d %d",&n,&m);
29 for(int i=1;i<=n;i++){
30 pre[i]=i;//每个点相互独立,上级都是自己
31 num[i]=1;
32 step[i]=0;
33 }
34 char c;
35 int a,b,d;
36 while(m--){
37 getchar();
38 scanf("%c",&c);
39 if(c=='T'){
40 scanf("%d %d",&a,&b);
41 f1=find(a);
42 f2=find(b);
43 if(f1!=f2){
44 pre[f1]=f2;
45 step[f1]++;//a的所有龙珠移到了b,步数+1
46 num[f2]+=num[f1];//f1的所有龙珠传给上级f2
47 num[f1]=0;//f1中龙珠数归0
48 }
49 }
50 else if(c=='Q'){
51 scanf("%d",&d);
52 int x=find(d);
53 printf("%d %d %d\n",x,num[x],step[d]);
54 }
55 }
56 }
57 return 0;
58 }
3、种类并查集
在前面简单并查集的基础上增加一个vis[i] ,记录 i 和同一集合的他的祖先的关系。
典型例题:POJ 1703 Find them,Catch them
http://poj.org/problem?id=1703
题解:给你一些数据 D a b 表示他们不是同伙,A a b 表示询问a 和 b之间的关系:同伙,不是同伙,不知道。
代码:
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 #include<algorithm>
6 #include<vector>
7 #define ll long long
8 using namespace std;
9
10 const int N = 100005;
11 int father[N];
12 bool vis[N];//记录性别,同性(同伙)为true,异性(不同伙)为false
13
14 int Find(int x, bool &sex)//返回父亲和父亲性别
15 {
16 sex = true;
17 int r = x;
18 while(x != father[x])
19 {
20 if(vis[x] == false)
21 sex = !sex;
22 x = father[x];
23 }
24 father[r] = x;//状态压缩
25 vis[r] = sex;//
26 return x;
27 }
28
29 int main()
30 {
31 int T;
32 scanf("%d", &T);
33 while(T--)
34 {
35 for(int i = 0; i < N; i++)
36 {
37 father[i] = i;
38 vis[i] = false;
39 }
40
41 int n, m;
42 scanf("%d %d", &n, &m);
43 getchar();
44 while(m--)
45 {
46 char c;
47 int x, y;
48 scanf("%c %d %d", &c, &x, &y);
49 getchar();
50 bool flag1 = false, flag2 = false;//这两父亲的性别
51 int f1 = Find(x, flag1);
52 int f2 = Find(y, flag2);
53 if(c == 'D')
54 {
55 father[f1] = f2;//将两个集合合并
56 vis[f1] = flag1 ^ flag2;//同为同性或同为异性都是一伙儿的
57 }
58 else
59 {
60 if(f1 == f2)//都在集合里就能判断是否一伙
61 {
62 if(flag1 == flag2) printf("In the same gang.\n");
63 else printf("In different gangs.\n");
64 }
65 else//有一个不在集合里就莫得办法了
66 printf("Not sure yet.\n");
67 }
68 }
69 }
70
71 return 0;
72 }
典型例题:HDU 1829 A Bug's Life
http://acm.hdu.edu.cn/showproblem.php?pid=1829
题解:给你一些数据,表示两人是情侣关系。1和2,2和3分别是情侣关系(不用管2踏了几只船),假如再来个关系1和3,哎呀不用怀疑,这俩肯定是好基友。
题目就是在询问你能不能发现好基友。
本题和上面的 POJ 1703 很像,代码只是做了稍稍改变。
代码:
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 #include<algorithm>
6 #include<vector>
7 #define ll long long
8 using namespace std;
9
10 const int N = 1000005;
11 int father[N];
12 bool vis[N];//记录性别,同性(同伙)为true,异性(不同伙)为false
13
14 int Find(int x, bool& sex)//返回父亲和父亲性别
15 {
16 sex = true;
17 int r = x;
18 while (x != father[x])
19 {
20 if (vis[x] == false)
21 sex = !sex;
22 x = father[x];
23 }
24 father[r] = x;//状态压缩
25 vis[r] = sex;//
26 return x;
27 }
28
29 int main()
30 {
31 int T;
32 scanf("%d", &T);
33 int t = 1;
34 while (T--)
35 {
36 for (int i = 0; i < N; i++)
37 {
38 father[i] = i;
39 vis[i] = false;
40 }
41
42 int n, m;
43 scanf("%d %d", &n, &m);
44
45 bool flag = true;
46 while (m--)
47 {
48 int x, y;
49 scanf("%d %d", &x, &y);
50
51 if(flag)
52 {
53 bool flag1 = false, flag2 = false;//这两父亲的性别
54 int f1 = Find(x, flag1);
55 int f2 = Find(y, flag2);
56 if (f1 == f2)//都在集合里就能判断是否一伙
57 {
58 if (flag1 == flag2) flag = false;
59 }
60 else
61 {
62 father[f1] = f2;
63 vis[f1] = flag1 ^ flag2;
64 }
65 }
66 }
67
68 printf("Scenario #%d:\n", t++);
69 if (flag)
70 printf("No suspicious bugs found!\n\n");
71 else
72 printf("Suspicious bugs found!\n\n");
73
74 }
75
76 return 0;
77 }
4、带权并查集
就是比简单并查集多了一个数组d[N],用来记录当前节点到根节点的有向距离。
因此在Find函数里,还要注意逐层累加。实现的代码可参考下面例题的代码。
经典例题:HDU 2818 Building-Block
http://acm.hdu.edu.cn/showproblem.php?pid=2818
题解:有n个木块,开始分成n堆。读入M a b 就将 a 所在堆的木块放在 b 堆的上面,在同一堆的话就不用管了。读入C a 就输出此时木块 a 下面的木块的个数。
代码:
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 #include<algorithm>
6 #include<vector>
7 #define ll long long
8 using namespace std;
9
10 const int N = 30005;
11 int father[N];//她老板
12 int num[N];//所在公司员工总数
13 int d[N];//在这个集合里的地位(数字越大,地位越低,位置越靠后)
14
15 int Find(int x)
16 {
17 if (x == father[x]) return x;
18 else
19 {
20 int t = father[x];
21 father[x] = Find(father[x]);
22 if(t != father[t])//目前的老板不是终极大BOSS
23 d[x] += d[t] - 1;//他的排名还要往后
24 //(假老板原来在他们这一堆里排第一,现在大BOSS回来了,他的位置现在在d[t]这个位置,往后走了d[t] - 1个位置,他手下的员工地位自然也要后退这么多)
25 }
26 return father[x];
27 }
28
29 int main()
30 {
31 std::ios::sync_with_stdio(false);
32 int n;
33 cin >> n;
34
35 for (int i = 0; i < N; i++)
36 {
37 father[i] = i;
38 d[i] = 1;
39 num[i] = 1;
40 }
41
42 while (n--)
43 {
44 char c;
45 cin >> c;
46 if (c == 'M')
47 {
48 int x, y;
49 cin >> x >> y;
50
51 int f1 = Find(x);
52 int f2 = Find(y);
53 if(f1 != f2)
54 {
55 father[f2] = f1; //f1公司吞并了f2,让f1做f2的大老板
56 d[f2] = num[f1] + 1;//f2的位置在f1所有员工之后一位
57 num[f1] += num[f2];//将f2旗下的人都交给新老板f1
58 }
59 }
60 else
61 {
62 int m;
63 cin >> m;
64 int cnt = Find(m);
65 cout << num[cnt] - d[m] << endl;//公司总人数减去她的位置,就是地位在她之下的人的数量
66 }
67 }
68 return 0;
69 }
Loading...