3696 构造有向无环图(拓扑排序)

1. 问题描述:

给定一个由 n 个点和 m 条边构成的图。不保证给定的图是连通的。图中的一部分边的方向已经确定,你不能改变它们的方向。剩下的边还未确定方向,你需要为每一条还未确定方向的边指定方向。你需要保证在确定所有边的方向后,生成的图是一个有向无环图(即所有边都是有向的且没有有向环的图)。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。每组数据第一行包含两个整数 n,m。
接下来 m 行,每行包含三个整数 t,x,y,用来描述一条边的信息,其中 t 表示边的状态,如果 t = 0,则表示边是无向边,如果 t = 1,则表示边是有向边。x,y 表示这条边连接的两个端点,如果是有向边则边的方向是从 x 指向 y。保证图中没有重边(给定了 (x,y),就不会再次出现 (x,y) 或出现 (y,x))和自环(不会出现 x = y 的情况)。

输出格式

对于每组数据,如果无法构造出有向无环图,则输出一行 NO。否则,先输出一行 YES,随后 m 行,每行包含两个整数 x,y,用来描述最终构造成的有向无环图中的每条边的具体方向(x 指向 y),边的先后顺序随意。注意,已经确定方向的边,不能更改方向。如果答案不唯一,输出任意合理方案均可。

数据范围

对于前三个测试点,1 ≤ n,m ≤ 10。
对于全部测试点,1 ≤ T ≤ 20000,2 ≤ n ≤ 2 × 10 ^ 5,1 ≤ m ≤ min(2 × 10 ^ 5,n(n − 1)2),0 ≤ t ≤ 1,1 ≤ x,y ≤ n。
保证在一个测试点中,所有 n 的和不超过 2 × 10 ^ 5,所有 m 的和不超过 2 × 10 ^ 5。

输入样例:

4
3 1
0 1 3
5 5
0 2 1
1 1 5
1 5 4
0 5 2
1 3 5
4 5
1 1 2
0 4 3
1 3 1
0 2 3
1 2 4
4 5
1 4 1
1 1 3
0 1 2
1 2 4
1 3 2

输出样例:

YES
3 1
YES
2 1
1 5
5 4
2 5
3 5
YES
1 2
3 4
3 1
3 2
2 4
NO
来源:https://www.acwing.com/problem/content/description/3699/

2. 思路分析:

分析题目可以知道如果有向边中已经存在环了,此时无论如何都不可能构成有向无环图,也即无解,我们可以使用拓扑排序判断当前有向图中是否存在环,如果存在环输出"NO"即可,如果存在拓扑排序可以发现一定是有解的因为之前的有向边一定是无环的,而无向边可以从前往后指则一定可以构造出一个有向无环图,所以一定是有解的,我们在遍历每条边的时候,分有向边和无向边将其存储下来,如果存在拓扑排序先输出所有的有向边,然后输出无向边此时需要根据拓扑排序的编号较小的节点指向编号较大的节点即可。

3. 代码如下:

import collections
from typing import List


class Solution:
    # 拓扑排序
    def topsort(self, n: int, d: List[int], q: List[int], g: List[List[int]]):
        queue = collections.deque()
        count = 0
        for i in range(1, n + 1):
            if d[i] == 0:
                queue.append(i)
        while queue:
            p = queue.popleft()
            count += 1
            q.append(p)
            for next in g[p]:
                d[next] -= 1
                if d[next] == 0:
                    queue.append(next)
        return count == n

    def process(self):
        T = int(input())
        while T > 0:
            n, m = map(int, input().split())
            # edge存储无向边, dir_edge存储有向边
            edge = list()
            dir_edge = [list() for i in range(n + 10)]
            # q记录拓扑排序的序列
            q = [0]
            # 记录节点的入度
            d = [0] * (n + 10)
            for i in range(m):
                t, x, y = map(int, input().split())
                if t == 0:
                    edge.append((x, y))
                else:
                    dir_edge[x].append(y)
                    d[y] += 1
            if not self.topsort(n, d, q, dir_edge):
                print("NO")
            else:
                # 输出有向边
                print("YES")
                for i in range(1, n + 1):
                    for x in dir_edge[i]:
                        print(i, x)
                # 输出无向边
                pos = [0] * (n + 10)
                for i in range(1, n + 1):
                    pos[q[i]] = i
                for i in range(len(edge)):
                    a, b = edge[i][0], edge[i][1]
                    # 由编号较小的指向编号较大的
                    if pos[a] > pos[b]:
                        b, a = a, b
                    print(a, b)
            T -= 1


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值