• 赛码网算法:认老乡


    最近在赛码网上做算法题,看到这样一道题,经过不断的学习,最后解决了。把我的思想和代码给大家分享一下~

    认老乡
    题目描述
    大学的同学来自全国各地,对于远离家乡步入陌生大学校园的大一新生来说,碰到老乡是多么激动的一件事,
    于是大家都热衷于问身边的同学是否与自己同乡,来自新疆的小赛尤其热衷。但是大家都不告诉小赛他们来自哪里,
    只是说与谁同乡,从所给的信息中,你能告诉小赛有多少人确定是她的同乡吗?

    输入
    每个测试实例首先包括2个整数,N(1 <= N <= 1000),M(0 <= M <= N*(N-1)/2),代表现有N个人(用1~N编号)和M组关系;
    在接下来的M行里,每行包括2个整数,a,b,代表a跟b是同乡;
    当N = 0,M = 0输入结束;
    已知1表示小赛本人。
    样例输入
    3 1
    2 3
    5 4
    1 2
    3 4
    2 5
    3 2
    0 0
    输出
    对于每个测试实例,输出一个整数,代表确定是小赛同乡的人数。
    样例输出
    0
    4
    时间限制
    C/C++语言:1000MS其它语言:3000MS
    内存限制
    C/C++语言:65536KB其它语言:589824KB

    作为一个没有搞过算法竞赛的普通大学生,当我拿到这道题的时候,自然的向数据机构去靠拢。

    其实这就是一道图论问题。我们把每个人当成一个点,如果两个人是同乡,这两个点之间有一条通路。

    根据输入用例,把所有同乡关系输入之后,也就能确定一个图当中有多少个点,哪些点之间是有连线(通路)的。

    最后我们要统计小赛同学的同乡,换句话说,我们要看与1号点直接和间接有路、能到达的点的个数、也就是小赛同乡的个数。

    分析完理论,说点实际的:

    最开始拿到这样一道题,想到是图论,我想到的办法就是数据结构当中的邻接表去描述这个图论。

    邻接表是什么样的呢?我带没有数据结构基础的朋友去理解一下:

      有一个点表,可以用数组实现。比如这道题,我们可以开一个大数组,0号不存东西,下标就代表同学的编号。数组每个值存一个链表,链表的值是跟这个点直接相连关系的点的下标。

      在这道题中,比如,1号元素代表1号学生,1号数组值存一个集合,集合内的元素是跟1号元素有通路的点的编号。

      按照输入,向定点表的集合里添加数字。输入结束后,图就构建起来了。每一个元素代表一个点。元素存的集合里面元素就是跟它有通路的其他点。

      最后我们查询跟1号点直接或和间接相连的点的个数,也就是它同乡的个数。

      方法是:遍历每一个元素,如果这个元素在1号元素的集合里,或者这个元素存的集合里的值有在1号元素集合里的,或者有1号元素的,那都是1号元素的老乡。最后统计一个total就可以了。

    按照上面的思想 我分别用c和python实现了:

    邻接表的思想显而易见,我用python来实现:

     1 coding:utf8
     2 mn= raw_input().strip(" ").split(" ")
     3 #接下来m个人    n组关系
     4 m = int (mn[0] )
     5 n = int (mn[1] )
     6 
     7 # 如果m n 都是0 退出程序
     8 if (m,n) == (0,0) :
     9     exit()
    10 
    11 #初始化一个 编号1到m的邻接表 0号不存数据
    12 # 1到m号人每个人拥有一个集合 集合里放着这个人老乡的编号 接下来输入数据的时候向集合里面添加老乡
    13 people = [ set() for i in range(m+1) ]
    14 #循环输入n个关系
    15 for i in range(n):
    16     #输入的关系放入rel列表当中
    17     rel = [ int(i) for i in raw_input().strip(" ").split(" ") ]
    18     #给邻接表当中 每一个人添加老乡编号 rel[0] 和 rel[1]是老乡  把rel[1] 的数值 放进rel[0]的集合当中
    19     people[rel[0]].add(rel[1])
    20 
    21 #遍历邻接表把1号的直接 间接的老乡 全都添加到1号的集合当中
    22 for i in range(2,m+1):
    23     # i 代表人的编号从2到m(不查看1号自己)
    24     # 如果 编号i在 people[1]中 说明i是1的同乡, 把i的同乡全都加入到people[1]里
    25     # 如果i的同乡有在 1 的同乡集合里的 说明他们是间接同乡 也把i号的同乡加入people[1]
    26     if i in people[1] or (people[i] & people[1] ):
    27         people[1] |= people[i]
    28         people[1].add(i)
    29 #最后 1号同学自己的集合里面元素的个数就是他老乡的个数
    30 print(len(people[1]))

    但是,当我提交代码的时候,发现邻接表的方法,是超出时间限制的。

    至就是一个老老实实学习的大学生的弊端,跟搞过算法的同学相比确实有需要学习的东西。

    经过不断查询,原来这种图论,某个点的连通问题可以用"并查集"的方法来解决。

    并查集也是解决图论思想的一种时间复杂度很低的算法,思想是:

    根据输入,确定点和点之间的连通。当点之间连通的时候,我们就把他们放到同一个集合当中。最后1所在集合的其他元素的个数就是1号元素老乡的数量。

    具体怎么实现呢:

      对于有n个编号点的图,我们开一个编号从0到n的数组。编号0我们不存数据。

      我们把1到n号的值都初始化成自己的编号,即下表为n的值里面存n。代表每个点跟自己是连通的。

      接下来根据输入,我们能确定某两个点的是连通的,这个时候,我们选定其中一个做根,另一个做叶子。叶子里面存根的编号。叶子和根一定是相连通的。根存自己的编号。

      随着输入关系的增多,我们尽量让叶子和跟的层数保持最低,对于多个点有连关系,我们选定其中一个做根,根的数值是自己的编号,其他所有叶子都存这一个根的编号。这样我们就能确定根和所有叶子都是连通的。

    按照上面的思想,再用python去实现一下,这个是并查集解决图论问题的思想:

     1 #coding:utf8
     2 
     3 def find(x):
     4     global relation
     5     if relation[x]!= x:
     6         relation[x] = find(relation[x])
     7     return relation[x]
     8 
     9 while True:
    10     mn= input().strip(" ").split(" ")
    11     #接下来m个人    n组关系
    12     m = int (mn[0] )
    13     n = int (mn[1] )
    14     # 如果m n 都是0 退出程序
    15     if (m,n) == (0,0) :
    16         exit()
    17 
    18     #初始化一个列表 ,编号i 里面存的与i有亲戚关系的同学,初始化的时候存着自己, 0号没有意义
    19     relation = [ i for i in range( m+1 ) ]
    20 
    21     #循环输入n个关系
    22     for i in range(n):
    23         #输入的关系放入rel列表当中
    24         rel = [ int(i) for i in input().strip(" ").split(" ") ]
    25         a = rel[0]
    26         b = rel[1]
    27         a = find(a)
    28         b = find(b)
    29         relation[a] = b
    30 
    31     sum = 0
    32     root = find(1)
    33     for i in range(2,m+1):
    34         if find(i) == root:
    35             sum +=1
    36 
    37     print(sum)

    这段代码交上去,发现还是超时,无奈,最后用c++语言去实现了一下,总算是通过了。

    c++语言代码如下:

     1 #include "iostream"
     2 using namespace std;
     3 int person[1001];
     4 
     5 //查找并维护连通图的方法
     6 int findroot( int a ){
     7     //要使能够连通的所有节点选一个做根,让所有其他节点指向这个跟,深度减小 方便遍历
     8     if( person[a] != a ){
     9         //让叶子节点全都存一个根节点的编号
    10         person[a] = findroot( person[a] );
    11     }
    12     return person[a];
    13 
    14 }
    15 
    16 
    17 int main(){
    18     int m, n;
    19     while(cin>>m>>n){
    20         if( m == 0 && n == 0 ){
    21             break;
    22         }
    23         //初始化一个集合,每个元素的可连通对象都是自己
    24         for( int i = 1; i <= m; i++){
    25             person[i] = i;
    26         }
    27 
    28         for( int i = 0; i < n ;i++ ){
    29             int a,b;
    30             cin>>a>>b;
    31             //输入了一组关系,我们调用方法,找到根节点,把他们都赋值根节点的编号
    32             a = findroot(a);
    33             b = findroot(b);
    34 
    35             person[a] = b;
    36         }
    37         int sum = 0;
    38         int root = findroot(1) ;
    39         for(int i = 2 ; i <= m ;i++ ){
    40                 //最后统计一下与1号节点根节点相同的有哪些点。也就是同乡的数量。
    41             if(findroot(i)==root){
    42                 sum ++;
    43             }
    44         }
    45         cout<<sum<<endl;
    46     }
    47     return 0;
    48 }
  • 相关阅读:
    [译]Chapter 3 Understanding Controllers
    Effiective C# Item1 : 使用属性代替成员变量
    终于出版了
    《Thinking in UML》读书笔记之一
    【译】ClickOnce部署概述
    [译]Chapter 2 Building a Simple ASP.NET MVC Application
    开始翻译《Application Architecture Guide 2.0》
    [译]Chapter 1 An Introduction to ASP.NET MVC(1)
    [译]Chapter 1 An Introduction to ASP.NET MVC(3)
    Effective C# Item4:使用Conditional特性代替#if条件编译
  • 原文地址:https://www.cnblogs.com/Lin-Yi/p/7351947.html
Copyright © 2020-2023  润新知