构造有向无环图

构造有向无环图

给定一个由 $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 \leq n,m \leq 10$。
对于全部测试点,$1 \leq T \leq 20000$,$2 \leq n \leq 2 \times {10}^{5}$,$1 \leq m \leq \min \left(2 \times {10}^{5},\frac{n(n−1)}{2} \right)$,$0 \leq t \leq 1$,$1 \leq x,y \leq n$。
保证在一个测试点中,所有 $n$ 的和不超过 $2 \times {10}^{5}$,所有 $m$ 的和不超过 $2 \times {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

 

解题思路

  先只考虑有向边,如果有向边构成的图本身就存在环,那么不管无向边怎么定义方向,整个图还是存在环的。因此如果有向边已经构成环,那么一定无解。

  对于有向边不构成环的情况,假设整个图是一个拓扑图,那么一定存在拓扑序。对于一个拓扑序,每条有向边一定是从前指向后的,而无向边一定连接某两个点(不存在自环),因此我们可以让每条无向边也从前指向后,仍然是个拓扑序,因此构造的整个图是个拓扑图。

  因此如果有向边无环,那么我们一定能够构造出一组解使得整个图是拓扑图,因此必然有解。

  AC代码如下,时间复杂度为$O(n+m)$:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 2e5 + 10;
 5 
 6 int head[N], e[N], ne[N], idx;
 7 int deg[N];
 8 int x[N], y[N]; // 存无向边
 9 int q[N];
10 int p[N];
11 
12 void add(int v, int w) {
13     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
14 }
15 
16 void solve() {
17     int n, m;
18     scanf("%d %d", &n, &m);
19     idx = 0;
20     memset(head, -1, n + 10 << 2);
21     memset(deg, 0, n + 10 << 2);
22     int sz = 0;
23     while (m--) {
24         int t, v, w;
25         scanf("%d %d %d", &t, &v, &w);
26         if (t) add(v, w), deg[w]++; // 建有向边的图
27         else x[sz] = v, y[sz] = w, sz++;    // 存无向边
28     }
29     int hh = 0, tt = -1;
30     for (int i = 1; i <= n; i++) {
31         if (!deg[i]) q[++tt] = i;
32     }
33     while (hh <= tt) {  // 对有向边构成的图跑一遍拓扑排序
34         int t = q[hh++];
35         for (int i = head[t]; i != -1; i = ne[i]) {
36             if (--deg[e[i]] == 0) q[++tt] = e[i];
37         }
38     }
39     if (tt != n - 1) {  // 如果有环说明队列中的点数小于n
40         printf("NO\n");
41         return;
42     }
43     printf("YES\n");
44     for (int i = 1; i <= n; i++) {  // 输出有向边
45         for (int j = head[i]; j != -1; j = ne[j]) {
46             printf("%d %d\n", i, e[j]);
47         }
48     }
49     for (int i = 0; i < n; i++) {   // 此时队列中的元素就是拓扑序,求每个点在拓扑序中的位置
50         p[q[i]] = i;
51     }
52     for (int i = 0; i < sz; i++) {
53         if (p[x[i]] > p[y[i]]) swap(x[i], y[i]);    // 无向边的方向定义为拓扑序的从左往右
54         printf("%d %d\n", x[i], y[i]);
55     }
56 }
57 
58 int main() {
59     int t;
60     scanf("%d", &t);
61     while (t--) {
62         solve();
63     }
64     
65     return 0;
66 }

 

参考资料

  AcWing 3696. 构造有向无环图(蓝桥杯集训·每日一题):https://www.acwing.com/video/4642/

posted @ 2023-03-08 20:01  onlyblues  阅读(144)  评论(0编辑  收藏  举报
Web Analytics