[拓扑排序] aw3696. 构造有向无环图(拓扑排序+memset使用坑点+aw周赛004_3)

Posted Ypuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[拓扑排序] aw3696. 构造有向无环图(拓扑排序+memset使用坑点+aw周赛004_3)相关的知识,希望对你有一定的参考价值。

1. 题目来源

链接:3696. 构造有向无环图

相关链接:

2. 题目解析

好题,DAG 与拓扑排序。

先考虑有向边,如果有向边都存在环的话,由于我们不能更改其方向,所以必然存在环,则一定出错

若有向边考虑完毕后没环的话,那么可以将拓扑序写出来,将无向边从前往后赋予方向即可,一定可以构造出来拓扑序


本题不保证给定的图是连通的,在处理中没啥影响。

h[N] 作为头数组,会用到 1~n 这些下标,故在 memset 的时候是要初始化 n+1 个。 同理 d[n]

这个也是一个坑点…


细节:

  • 注意不要直接使用 memset,因为 T2e5 的,外部数组大小也是 2e5 的,memset 的时间是线性的,需要两次 memset,但是即便这样时间复杂度也到达了 2e5*2*2e5=8e10 的恐怖复杂度,回过,但特别容易被卡常数。
  • 循环初始化就行了,但是注意要初始化 n+1 个。

时间复杂度: O ( n + m ) O(n+m) O(n+m)

空间复杂度: O ( n ) O(n) O(n)


#include <bits/stdc++.h>

using namespace std;

const int N = 2e5+5, M = 2e5+5;

int n, m, k;        // k 用来记录无向边的个数
int h[N], e[M], ne[M], idx;
int d[N];           // 统计入度
struct Edge {
    int a, b;
} edges[M];         // 存无向边
int q[N], pos[N];   // 队列,存储队列中拓扑排序各点的下标

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort() {
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ ) 
        if (!d[i])
            q[ ++ tt] = i;
        
    while (hh <= tt) {
        int t = q[hh ++ ];
        
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            d[j] -- ;                   // h[t]的入度为0,删掉与它相关的所有出边
            if (!d[j]) q[ ++ tt] = j;   // 如果与 h[t] 删边完的出边入度是 0 的话,就可以将其加入队列,更新其它点了
        }
    }
    
    // 若所有点都入队,即存在拓扑排序
    return tt == n - 1;
}

int main() {
    int T;
    scanf("%d", &T);
    
    while (T -- ) {
        scanf("%d%d", &n, &m);
        memset(h, -1, (n + 1) * 4);     // 不要直接 sizeof h,memset 时间是线性的
        memset(d, 0, (n + 1) * 4);      // 下标是从 1 开始的,(a, b) 下标从 1 开始,用到 h[1]~h[n]
        
        idx = k = 0;
        while (m -- ) {
            int t, a, b;
            scanf("%d%d%d", &t, &a, &b);
            if (!t) edges[k ++ ] = {a, b};      // 无向边
            else {                              
                add(a, b);                      // 有向边
                d[b] ++ ;                       // 入度加 1
            }
        }
        
        if (!topsort()) puts("NO");             // 有向边不可以拓扑排序,则不能构成DAG
        else {
            puts("YES");
            
            // 输出所有有向边
            for (int i = 1; i <= n; i ++ ) 
                for (int j = h[i]; ~j; j = ne[j]) 
                    printf("%d %d\\n", i, e[j]);
                    
            
            // 无向边定义方向,拓扑排序前方的边方向指向后方
            for (int i = 0; i < n; i ++ ) pos[q[i]] = i;    // 记录拓扑排序每个点的下标
            
            // 遍历所有无向边
            for (int i = 0; i < k; i ++ ) {
                int a = edges[i].a, b = edges[i].b;
                if (pos[a] > pos[b]) swap(a, b);            // 方向从前向后
                printf("%d %d\\n", a, b);          
            }
        }
    }
    
    return 0;
}

以上是关于[拓扑排序] aw3696. 构造有向无环图(拓扑排序+memset使用坑点+aw周赛004_3)的主要内容,如果未能解决你的问题,请参考以下文章

一个有向无环图的拓扑排序序列是否唯一的

有向无环图的判定及拓扑排序

有向无环图

使用 拓扑排序进行有向无环图 任务关系拆解,实现任务编排

CSU 1804: 有向无环图 拓扑排序 图论

使用 拓扑排序进行有向无环图 任务关系拆解,实现任务编排