某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市政府 "村村通工程" 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?
输入包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 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。