通信系统(并查集以及图论解法)

题目描述

某市计划建设一个通信系统。按照规划,这个系统包含若干端点,这些端点由通信线缆链接。消息可以在任何一个端点产生,并且只能通过线缆传送。每个端点接收消息后会将消息传送到与其相连的端点,除了那个消息发送过来的端点。如果某个端点是产生消息的端点,那么消息将被传送到与其相连的每一个端点。
为了提高传送效率和节约资源,要求当消息在某个端点生成后,其余各个端点均能接收到消息,并且每个端点均不会重复收到消息。
现给你通信系统的描述,你能判断此系统是否符合以上要求吗?

输入

输入包含多组测试数据。每两组输入数据之间由空行分隔。
每组输入首先包含2个整数N和M,N(1<=N<=1000)表示端点个数,M(0<=M<=N*(N-1)/2)表示通信线路个数。
接下来M行每行输入2个整数A和B(1<=A,B<=N),表示端点A和B由一条通信线缆相连。两个端点之间至多由一条线缆直接相连,并且没有将某个端点与其自己相连的线缆。
当N和M都为0时,输入结束。

输出

对于每组输入,如果所给的系统描述符合题目要求,则输出Yes,否则输出No。

样例输入

4 3
1 2
2 3
3 4

3 1
2 3

0 0

样例输出

Yes
No

从题目意思来看,这道题目关键就是要保证两个点

1: 这是一张无环图

2: 这张图是一张连通图

综上,这就变成一个并查集的模板题,每次加进来一条边只要判断两个顶点的根节点是否相同,如果相同说明遇到了环,如果不相同,如果等所有的边都搜完了之后我们知道,这里面是没有环的,但是我们还要判断这张图是否为一个连通图. 就从最开始的点进行find如果所有的点他们只有一个根节点,说明这是一个连通图,如果不只有一个根节点,说明不是.

下面贴上并查集模板代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1005;
int head[maxn];
int n,m;
int find(int x){
    return (head[x]==x)? x : (head[x] = find(head[x]));
}
int main(){
    int a,b,flag,tot;
    int c,d;
    while(scanf("%d%d",&n,&m)!=EOF&&n){
        flag = 0,tot=0;
        for(int i=0;i<=n;i++)
            head[i] = i;
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            c = find(a),d = find(b);
            if(!flag&&c == d){
                flag = 1;
            }
            if(c != d) head[c] = d;
        }
        for(int i=1;i<=n;i++)
            if(i == find(i)) tot++;
        if(flag || tot>1) cout<<"No"<<endl;
        else cout<<"Yes"<<endl;
    }
    return 0;
}

虽然用并查集的代码过了,但是我们不能仅仅只满足于AC题目呀,总得搞点事情做,所以就尝试用图的方法来做.

对于判断一个图是否联通这个非常的好做.

对于一个有向图而言, 我们用一个In[]数组来记录每个节点的入度,然后我们反复的找度数为0的点, 每找到一个度数为0的点,就将这个点能够到达的所有点的度数都减一, 直到没有度数为0的点或者所有的点都已经遍历完了为止. 我们知道, 如果没有入度为0的点了,要么就是所有点都废弃了(也就是都遍历完了),要么就是还有一个或者多个环, 因为只有在这个图是一个环的情况下才会没有入度为0的点.

但是该题目是一个无向图, 而且还要判断这个图是否为一个连通图, 所以需要在前面的算法基础上稍作改变

同样定义一个in数组存储每个节点的度数, 这里是度数,无向图一条边提供两度, 起点+1度终点+1度, 然后又会发现, 子图为一个环的情况下,这个环上的点的度数总是>=2. 但是这里还有一个注意点就是当一个图一直搜直到搜到最后一个点的时候你会发现这个最后剩下的点的度数总是为0, 因为只剩它一个点了, 它的度数当然为0, 这个就可以成为我们判断里面有多少个连通分量的一个标记, 如果整张图就这一个是连通的,那么最后肯定只会剩一个度数为0的点, 如果内部有很多个极大连通分量,那么在搜索过程中有多少个连通块, 最后我们记录的度数为0的点总是会等于这个连通块的数目. 所以,这个地方代码就在另外用一个变量来存储访问到多少个度数为0的点就行了.

下面贴上代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector> 
using namespace std;
const int maxn = 1005;
int n,m;
int in[maxn]; //记录度数的数组 
vector<vector<int> > mp;
int main(){
    int a,b;
    while(cin>>n>>m&&n){
    	mp.resize(n+1);//事先申请n个vector<> 
        memset(in,0,sizeof(in));
        for(int i=1;i<=m;i++){
            cin>>a>>b;
            mp[a].push_back(b);
			mp[b].push_back(a); 
            in[a]++;//a的度数+1 
            in[b]++;//b的度数+1 
        }
        int flag,f=0,tp=n;//记录有无环,f记录子集的个数 
        while(tp && flag){
            flag = 0;//记录这一次有没有搜到废弃点,如果说没有搜到废弃点那就说明所有的点都没有废弃点了下次再搜就是浪费时间 
            for(int i=1;i<=n;i++){
                if(in[i]==1||in[i]==0){
                    if(in[i] == 0) f++;		 //说明一个集合搜到最后一个点了,记录集合的个数 
                    int tmp = mp[i].size();
                    for(int j=0;j<tmp;j++)
                        in[mp[i][j]]--;//能够到达的点的度数减一 
                    flag = 1;
                    tp--;//将这个点给舍弃掉,防止下次再次搜到 
                    in[i] = -1;
                }
            }
        }
        if(!tp&&f==1) cout<<"Yes"<<endl;//也就是说所有的点都废弃掉了同时集合的个数等于1 
        else cout<<"No"<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lala__lailai/article/details/81320343