1471 牛奶工厂(floyd求解传递闭包、证明)

该博客介绍了如何解决一个与牛奶加工站网络相关的问题,其中每个加工站由单向通道连接。问题在于确定是否存在一个加工站,从其他所有加工站都能到达。解决方案包括使用Floyd-Warshall算法寻找传递闭包,以及通过分析出度为0的节点来找出关键加工站。代码示例提供了两种不同时间复杂度的算法实现,分别是O(n^3)和O(n)。
摘要由CSDN通过智能技术生成

1. 问题描述:

牛奶生意正红红火火!农夫约翰的牛奶加工厂内有 N 个加工站,编号为 1…N,以及 N−1 条通道,每条连接某两个加工站。(通道建设很昂贵,所以约翰选择使用了最小数量的通道,使得从每个加工站出发都可以到达所有其他加工站)。为了创新和提升效率,约翰在每条通道上安装了传送带。不幸的是,当他意识到传送带是单向的已经太晚了,现在每条通道只能沿着一个方向通行了!所以现在的情况不再是从每个加工站出发都能够到达其他加工站了。然而,约翰认为事情可能还不算完全失败,只要至少还存在一个加工站 i 满足从其他每个加工站出发都可以到达加工站 i。注意从其他任意一个加工站 j 前往加工站 i 可能会经过 i 和 j 之间的一些中间站点。请帮助约翰求出是否存在这样的加工站 i。

输入格式

输入的第一行包含一个整数 N,为加工站的数量。以下 N−1行每行包含两个空格分隔的整数 ai 和 bi,满足 1≤ ai,bi ≤ N 以及 ai≠bi。这表示有一条从加工站 ai 向加工站 bi 移动的传送带,仅允许沿从 ai 到 bi 的方向移动。

输出格式

如果存在加工站 i 满足可以从任意其他加工站出发都可以到达加工站 i,输出最小的满足条件的 i。否则,输出 −1。

数据范围

1 ≤ N ≤ 100

输入样例:

3
1 2
3 2

输出样例:

2
来源:https://www.acwing.com/problem/content/description/1473/

2. 思路分析:

分析题目可以知道我们我们已知的是一棵有向树(n个点n - 1条有向边),因为是有向边所以所有节点之间并不是一定是联通的,判断是否存在这样一个点使得其余点能够到达当前这个点,根据这个特点我们最容易想到的是floyd算法,floyd算法在求解任意两点之间的最短距离的时候可以用来求解传递闭包,也即可以判断任意两个点是否连通,时间复杂度为O(n ^ 3),所以我们可以先使用floyd算法求解出传递闭包然后枚举所有的点,判断其余点是否可以到达当前的点如果可以那么输出这个点,如果没有找到则输出-1。除了使用floyd算法求解传递闭包之外我们其实还可以使用一个更加优秀的O(n)的做法来解决,一般时间复杂度比较低的算法都需要挖掘一下题目中隐藏的性质,首先第一个比较容易得到的性质是:① 如果有解,那么解一定是唯一的,这个也比较好证明,如果存在一个点A,其余点都可以到达点A,还存在一个点B,其余点也可以到达点B,由于是一棵有向树所以AB之间一定是连通的,所以AB之间一定存在一条有向路径,由于方向是唯一的所以只能够从一个点走到另外一个点,也即不能是双向的道路,要想使得AB是连通的那么道路需要是双向的所以就矛盾了,所以如果存在解那么解一定是唯一的。第二个性质则需要画图找一下:如果有解那么比较容易推出出度为0的节点有且仅有一个,通过画图就可以推出来,那么出度为0的节点有且仅有一个是否是有解的充分条件呢?其实我们可以使用反证法证明出来出度为0的节点有且仅有一个是有解的充分条件:假设存在一个点不能够走到根节点那么肯定至少存在一个分支节点的出度也是等于0的这样就与出度为0的点有且仅有一个就矛盾了所以出度为0的节点有且仅有一个可以推出有解,所以是充要条件,所以我们只需要使用一个数组d统计一下所有点的出度判断是否只有一个点的出度为0如果满足说明当前点就是答案。

3. 代码如下:

floyd算法求解传递闭包:O(n ^ 3)

class Solution:
    def process(self):
        n = int(input())
        g = [[0] * (n + 10) for i in range(n + 10)]
        # 自己可以走到自己
        for i in range(1, n + 1): g[i][i] = 1
        for i in range(n - 1):
            a, b = map(int, input().split())
            g[a][b] = 1
        # floyd算法求解传递闭包, 注意节点编号是从1开始
        for k in range(1, n + 1):
            for i in range(1, n + 1):
                for j in range(1, n + 1):
                    # i是否可以到j
                    g[i][j] |= 1 if g[i][k] and g[k][j] else 0
        # 枚举所有的点, 第一个满足的点一定是最小的
        for i in range(1, n + 1):
            flag = 1
            for j in range(1, n + 1):
                # 判断其余点是否可以到达当前点i, 只要是有一个不能够到达j那么说明不满足条件
                if g[j][i] == 0:
                    flag = 0
                    break
            if flag: return i
        return -1


if __name__ == '__main__':
    print(Solution().process())

O(n)的做法,需要挖掘题目的一些性质:

class Solution:
    def process(self):
        n = int(input())
        # 统计节点的出度
        d = [0] * (n + 10)
        for i in range(n - 1):
            a, b = map(int, input().split())
            # 节点a的出度加1
            d[a] += 1
        count = idx = 0
        for i in range(1, n + 1):
            if d[i] == 0:
                count += 1
                idx = i
        # 判断出度为0的节点是否只有一个, 如果只有一个说明就是idx就是答案
        if count == 1:
            return idx
        else:
            return -1


if __name__ == '__main__':
    print(Solution().process())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值