村村通

题目描述

某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市* "村村通工程" 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?

输入格式

输入包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 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。

上一篇:PAT A1025 考生排名问题


下一篇:最全PR曲线、ROC曲线以及AUC计算公式详解