题目描述
某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市* "村村通工程" 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?
输入格式
输入包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 n 和道路数目 m ;随后的 m 行对应 m 条道路,每行给出一对用空格隔开的正整数,分别是该条道路直接相连的两个城镇的编号。简单起见,城镇从 1 到 n 编号。
注意:两个城市间可以有多条道路相通。
输出格式
对于每组数据,对应一行一个整数。表示最少还需要建设的道路数目。
思路
这是一个基本的并查集的题目,其基础操作有三:初始化;返回点所在的集合;合并两个集合。
可以把相互能够连通的城镇放在一个集合内,通过合并操作实现,一开始的初始集合都只有自己。
初始化:
for(int i=1;i<=n;i++){
rank[i]=0;
p[i]=i;
}
rank
数组用于存储表示集合的树的深度,p
数组用于存储当前节点的父亲节点,开始时父亲节点都是自身。
查操作:
int find_set(int x){
int i,px=x;
while(px!=p[px]) px=p[px];//找根节点
while(x!=px){
i=px;
p[x]=px;
x=i;//降低树的高度
}
return px;//返回根节点.
}
查操作不仅会返回所找点的根节点,并且还会将从该点到根节点上所经过的点的父亲节点都改为根节点,这样减少了下次查找的路径。
合并操作:
void Union(int x,int y){
x=find_set(x);
y=find_set(y);
if(rank[x]>rank[y]) p[y]=x;
else{
p[x]=y;
if(rank[x]==rank[y]) rank[y]++;
}
}
通过树的深度决定如何合并,不过这里仅更改了某一棵树的根节点,这棵树上的子节点的父亲节点并没有改为合并后的结果,所以之后找所有根的个数的时候还需要再更新一次。
主函数:
int main(){
//freopen("4.in","r",stdin);
int n,m;
int x,y;
int k;
scanf("%d%d",&n,&m);
while(n!=0){
for(int i=0;i<1004;i++)
base[i]=0;
k=0;
int flag;
//make-set
for(int i=1;i<=n;i++){
rank[i]=0;
p[i]=i;
}
int l=m;
while(l--){
scanf("%d%d",&x,&y);
Union(x,y);//并
}
//获得根的数目
base[1]=find_set(1);
for(int i=1;i<=n;i++){
////if(find_set(i)==i) k++;
flag=1;
for(int j=1;j<=k;j++){
if(base[j]==find_set(i)){
flag=0;
break;
}
}
if(flag) base[++k]=find_set(i);
}
printf("%d\n",k-1);
scanf("%d%d",&n,&m);
}
}
其中在找根的数目时,通过if(find_set(i)==i) k++;
更为简单方便,就很巧妙,不过我自己没想到,hhhh。