本文仅供参考学习使用,谢谢
问题描述:
Alice在玩乐高积木,这种积木能互相拼接到一起。Alice为每块积木编了号,为1,2,……n,她计划构造一个复杂的积木世界,因此按顺序在图纸上写下了需要互相拼接的积木编号。BOB看到了这份图纸,他想捣乱,想把所有的积木拼接成一个整体,他至少需要准备多少块积木?
- 输入:
数据有T组。每组第一行为n,m,表示这次拼接的积木数和拼接次数。接下来的m行都有两个数a,b,表示将积木a和b进行拼接。
- 输出:
有T行,每行表示对这组数据,BOB至少要准备的积木个数。
思路分析
按照并查集的思想只要两者之间有联系就将两者之间产生一条联系,如果两者之间已有联系则不用进行处理,最后查询每一个祖先节点所包含的节点个数(包括他自己),找到含有节点数最多的那个,即为所求
算法描述
- init函数:
先申请一个一维数组,每一个节点都代表第n个元素,每个节点上储存该节点的父亲节点的下标,初始时他的父亲节点下标是他自己
- union函数:
将两个节点之间建立一条联系,即将第一个节点的父亲节点化为第二个节点的父亲节点,如何判断是第一个节点还是第二个节点呢?可以利用循环或者递归找到该节点的高度,比较两者高度大小,高度小的节点的父亲节点设置为高度高的父亲节点的下标。(此时将该并查集视为一张无向图处理)(使用启发式合并)
- find函数:
在对两个节点是否需要进行union之前需要进行判断两个节点的祖先节点是否是同一个节点,如果是同一个节点,则不需要进行union合并两个节点的祖先节点。在查找两个节点的祖先节点时,可以利用循环判断 a[n]==n ? 如果不等于说明该节点并不是祖先节点,然后进行 n=a[n];
测试数据
- 数据1:
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
- 结果1:
1
0
2
998
- 数据2:
15 12
1 11
2 6
8 3
4 2
7 12
13 15
10 11
9 4
12 14
15 10
11 13
14 7
- 结果2:
4
代码
//
// main.cpp
// 拼接需要积木
//
// Created by xxc on 2020/4/2.
// Copyright © 2020 xxc. All rights reserved.
//
#include <iostream>
#define maxsize 1000000
using namespace std;
int list[maxsize];
int flag[maxsize];
int find(int x){
int ans(x);
while (list[ans]!=ans) {
ans=list[ans];
}
//路径压缩
int r(x),c(0);
while (list[r]!=r) {
c=list[r];
list[r]=ans;
r=c;
}
return ans;
}
void join(int a, int b){
int f_a(find(a)),f_b(find(b));
if (f_a==f_b) return;
//启发式合并
int aa(a),bb(b);
int count_a(0),count_b(0);
while (list[aa]!=aa) {
aa=list[aa];
count_a++;
}
while (list[bb]!=bb) {
bb=list[bb];
count_b++;
}
if (count_a<count_b) {//a的高度小 高度小的合并到高度大的
list[f_a]=f_b;
}else{
list[f_b]=f_a;
}
}
int count(int N){
int cnt(0);
for (int i=1; i<=N; i++)
if (flag[i]==1)
cnt++;
return cnt;
}
void init(int N, int M){
for (int i=0; i<=N; i++) {
list[i]=i;
}
for (int i=1; i<=M; i++) {
int a,b;
cin>>a>>b;
join(a, b);
}
for (int i=1; i<=N; i++){ //标记祖先节点
flag[i]=0;
flag[find(i)]=1;
}
}
int main(int argc, const char * argv[]) {
int N=0,M=0;
while (cin>>N>>M) {
init(N, M);
cout<<count(N)-1<<endl;
}
return 0;
}