|
|
@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
import java.util.Scanner;
|
|
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import java.util.TreeSet;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
将城镇抽象为顶点,将有道路联通的城镇划分到同一个集合中,将问题转化成求集合个数的问题
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class ChangTongEngine {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Scanner scanner = new Scanner(System.in);
|
|
|
|
|
|
|
|
while (scanner.hasNextInt()) {
|
|
|
|
|
|
|
|
int n = scanner.nextInt(); n表示城镇数量
|
|
|
|
|
|
|
|
if (n == 0) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int m = scanner.nextInt(); m表示道路数目
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
初始化一个并查集
|
|
|
|
|
|
|
|
UnionFind uf = new UnionFind(n+1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i m; i++) {
|
|
|
|
|
|
|
|
m1和m2表示两个城镇编号
|
|
|
|
|
|
|
|
int m1 = scanner.nextInt();
|
|
|
|
|
|
|
|
int m2 = scanner.nextInt();
|
|
|
|
|
|
|
|
合并已经建成的城市之间的道路
|
|
|
|
|
|
|
|
uf.union(m1, m2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TreeSet中元素是有序且不重复的
|
|
|
|
|
|
|
|
TreeSetInteger ts = new TreeSetInteger();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i = n; i++) {
|
|
|
|
|
|
|
|
ts.add(uf.find(i));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
n个节点至少需要n-1条边来连接
|
|
|
|
|
|
|
|
System.out.println(ts.size() - 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
并查集类
|
|
|
|
|
|
|
|
class UnionFind{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int[] parent; 记录结点的父亲
|
|
|
|
|
|
|
|
int[] height; 记录树的高度
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
有参数构造方法
|
|
|
|
|
|
|
|
初始化并查集
|
|
|
|
|
|
|
|
public UnionFind(int n) {
|
|
|
|
|
|
|
|
parent = new int[n];
|
|
|
|
|
|
|
|
height = new int[n];
|
|
|
|
|
|
|
|
for (int i = 0; i parent.length; i++) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parent[i] = i; 初始时,每个节点的父亲节点都是自己
|
|
|
|
|
|
|
|
height[i] = 0; 初始时,所有树高均为1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
查操作,找到元素x的根结点
|
|
|
|
|
|
|
|
public int find(int x) {
|
|
|
|
|
|
|
|
if (x != parent[x]) {
|
|
|
|
|
|
|
|
压缩路径:先找到根结点,再将查找路径上所有结点都挂到根结点下
|
|
|
|
|
|
|
|
parent[x] = find(parent[x]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent[x];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
合并操作
|
|
|
|
|
|
|
|
public void union(int x,int y) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int rootX = find(x);
|
|
|
|
|
|
|
|
int rootY = find(y);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如果两颗树的根结点相同时,说明两个节点在一个集合中
|
|
|
|
|
|
|
|
if (rootX == rootY) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
按高度合并
|
|
|
|
|
|
|
|
if (height[rootX] height[rootY]) {
|
|
|
|
|
|
|
|
parent[rootY] = rootX;
|
|
|
|
|
|
|
|
}else if (height[rootX] height[rootY]) {
|
|
|
|
|
|
|
|
parent[rootX] = rootY;
|
|
|
|
|
|
|
|
}else{
|
|
|
|
|
|
|
|
parent[rootY] = rootX;
|
|
|
|
|
|
|
|
如果高度相同则合并后高度加1
|
|
|
|
|
|
|
|
height[rootX]++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i parent.length; i++) {
|
|
|
|
|
|
|
|
if (parent[i] == rootX) {
|
|
|
|
|
|
|
|
遍历并查集中的结点,如果并查集中结点的祖先是x的祖先,则全部设置成y的祖先
|
|
|
|
|
|
|
|
parent[i] = rootY;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|