构造有向无环图
给定一个由 $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/
标签:10,idx,leq,int,构造,环图,方向 From: https://www.cnblogs.com/onlyblues/p/17195925.html