搜索与图论1.1-拓扑排序

一、简述

本节介绍一下拓扑排序的应用以及模板。

二、拓扑序

有向无环图,又称拓扑图,而拓扑图一定对应着拓扑序,比如有向图 \((1,2),(2,3),(1,3)\),前者指向后者,对应于拓扑序 \(1,2,3\)。基本模板类似 \(BFS\),如下

初始化队列 queue
所有入度为0的点入 queue
while queue不空
{
	取出队头元素t
	枚举t的所有出边j
		删除t到j并且j的入度减一
		如果j的入度为0
			j入队
}
如果队列中的元素个数为点的个数,则有拓扑序

模板题AcWing848.有向图的拓扑序列

题目描述

给定一个 \(n\) 个点 \(m\) 条边的有向图,点的编号是 \(1\)\(n\),图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 \(−1\)

若一个由图中所有点构成的序列 \(A\) 满足:对于图中的每条边 \((x,y)\)\(x\)\(A\) 中都出现在 \(y\) 之前,则称 \(A\) 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 \(n\)\(m\)

接下来 \(m\) 行,每行包含两个整数 \(x\)\(y\),表示存在一条从点 \(x\) 到点 \(y\) 的有向边 \((x,y)\)

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 \(−1\)

数据范围

\(1≤n,m≤10^5\)

输入样例
3 3
1 2
2 3
1 3
输出样例
1 2 3
解题思路

直接套板子就行。

C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 100010;

int n, m;
int h[N], e[M], ne[M], idx;
int q[N], hh, tt = -1;
int d[N];

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

bool topSort()
{
    for (int i = 1; i <= n; i ++)
        if (d[i] == 0)
            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] --;
            if (d[j] == 0) q[++ tt] = j;
        }
    }
    return tt == n - 1;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m --)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
        d[y] ++;
    }
    if (topSort())
        for (int i = 0; i <= tt; i ++)
            printf("%d ", q[i]);
    else printf("-1");
    return 0;
}

AcWing3696.构造有向无环图

题目描述

给定一个由 \(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,\frac{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
解题思路

首先构造有向无环图,那么我们就想到拓扑排序。首先我们先不考虑无向边,只考虑有向边,我们先对有向边构造拓扑序,如果无法构造拓扑序,那么无论是否考虑剩余的无向边,都无法构造拓扑序。那么如果可以构造,剩余的无向边,按照拓扑序加进即可。

C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 200010;

int T;
int n, m;
int h[N], e[M], ne[M], idx;
int q[N];
int d[N];
struct Edge
{
    int a, b;
} edge[M];
int pos[N];

void add(int x, int y)
{
    e[idx] = y;
    ne[idx] = h[x];
    h[x] = 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] --;
            if (!d[j]) q[++ tt] = j;
        }
    }
    return tt == n - 1;
}

int main()
{
    scanf("%d", &T);
    while (T --)
    {
        scanf("%d%d", &n, &m);
        memset(h, -1, (n + 1) * 4);
        memset(d, 0, (n + 1) * 4);
        idx = 0;
        
        int cnt = 0;
        
        while (m --)
        {
            int t, x, y;
            scanf("%d%d%d", &t, &x, &y);
            if (t == 0) edge[cnt ++] = {x, y};
            else
            {
                add(x, y);
                d[y] ++;
            }
        }
        if (!topSort()) puts("NO");
        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 < cnt; i ++)
            {
                int a = edge[i].a, b = edge[i].b;
                if (pos[a] > pos[b]) swap(a, b);
                printf("%d %d\n", a, b);
            }
        }
    }
    return 0;
}
posted @ 2023-03-06 23:00  Cocoicobird  阅读(20)  评论(0)    收藏  举报