“时光|

zsdqwq

园龄:3年6个月粉丝:9关注:17

拓扑排序学习笔记

前言

今天教练让我们做这玩意的题,发现原来这玩意有这么多花样玩法

more and more vegetables,what should I do?

本来想看看认识的人有没有写这个的,看到zpl写的

感觉提高组图论全是拓扑排序。

于是,来填坑了。

希望能讲清楚,不清楚的地方请在下面留言。

由于作者太菜,因此文章通俗易懂;


前置芝士

拓扑排序是对有向无环图(DAG) 上的节点进行排序,

简单来说其实就是在不改变节点先后顺序的前提下把这一张图拉成一条链

How Does Topo Works

拓扑排序最为经典的是Kahn算法

然而拓扑排序只在有向无环图(DAG) 上有结果;

下面我们来具体真实地模拟一下:

首先,先拿出所有入度为 0 的点,排放在最前面,

并且在原图中将他们删除;

我们以上面的图为例:

那么我们序列是这样增加的:

首先,最先取出的是 34

当然,最先取出的也可以是 43,这就证明了拓扑排序的不唯一

之后,更新剩下的点的入度:

然后取出入度为 02

最后取出 51

这时,序列就变成了:
3 4 2 1 5

来看一下这个过程,因为是有向无环图,而且删除的过程中不会出现不会产生环,那么就一直会存在入度为 0 点,珂以一直删除下去这道所有的点都被删没;

于是,可以给出下面一个复杂度为 O(m+n) 的实现,这里 n 是点数,m 为边数:(看没人用邻接表+队列,我来写一个吧)

int n, m;
vector<int>q[100005];
int r[100005];
queue<int>que;
void toposort() {
	for (int i = 1; i <= n; i++) {
		if (r[i] == 0) {
			que.push(i);
		}
	}
	while (que.empty()) {
		int x = que.front();
		que.pop();
		cout << x << " ";
		for (int i = 0; i < q[x].size(); i++) {
			int y = q[x][i];
			r[y]--;
			if (r[y] == 0) {
				que.push(y);
			}
		}
	}
}

代码里面主要不用邻接矩阵的原因是这东西很浪费,

直接用vector就可;

咋用这玩意

然后拓扑排序其实是可以简单的用来判环 的,在结尾加上返回值就可。

当然如果需要字典序最小就直接把queue改成priority_queue时间复杂度也会随之加上log

我们来看几道应用,

P4017

可以算是一道很基础的板子题,

看到题面中说

(这里的“最大食物链”,指的是生物学意义上的食物链,即最左端是不会捕食其他生物的生产者,最右端是不会被其他生物捕食的消费者。)

我们其实就珂以确定这一定是拓扑排序了,最左端其实就是入度为 0 的点,按照刚刚提到的思路。

其实我觉得这道题我们不需要做,因为这个做法实在是让我不知道说啥,但是看在也是一道这个类型的,就加里头吧

P1807

拓扑排序很经典的就是最长路

显然,有最长路的只有 DAG

其实就是一个普通的排序再加上一个选择边的问题

那么选择边的问题可以用 dp 来解决

其实转移方程就是:

min(a1+m1,a2+m2,,an+mn)

(注:其中,ai 代表的是入度为 i 的边,mi 就是对应的边)

总之就是很经典咧,可以看一下代码

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
int main()
{
	int n, m, r[1505], v[1505];
	vector<int>g[1505];
	vector<int>d[1505];
	queue<int>q;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;
		g[a].push_back(b);
		d[a].push_back(c);
		r[b]++;
	}
	for (int i = 2; i <= n; i++)
	{
		v[i] = -1e9;
		if (!r[i]) { 
			q.push(i); 
		}
	}
	while (!q.empty())
	{
		int x = q.front();
		q.pop();
		for (int i = 0; i < g[x].size(); i++) {
			if (!--r[g[x][i]]) { 
				q.push(g[x][i]); 
			}
		}
	}
	q.push(1);
	while (!q.empty())
	{
		int x = q.front();
		q.pop();
		for (int i = 0; i < g[x].size(); i++)
		{
			if (v[g[x][i]] < v[x] + d[x][i]) { 
				v[g[x][i]] = v[x] + d[x][i];
			}
			if (!--r[g[x][i]]) q.push(g[x][i]);
		}
	}
	if (v[n] == -1e9) cout << "-1";
	else cout << v[n];
	return 0;
}

下面这道题目还是很有意思的:

CF510C

通过对相邻的两个名字比较,就可以得到 n1 个关系,它们珂以构成一张图(暂且不管这到底有环没环),最后的字母表也满足这些关系。

如果存在环的话,可以用拓扑(见上);

如果不存在环的话,还可以用拓扑,拓扑排序就正解;

总结(?

在题目中其实就是要找到可以利用拓扑的地方(大部分DAG吧),然后再建图,知道题目具体要干啥,这样才不至于跑偏,最后输出。

总之吧,学好拓扑很有必要(

本文作者:zsdqwq

本文链接:https://www.cnblogs.com/wo-de-bo-ke-wo-zuo-zhu/p/toposort.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   zsdqwq  阅读(57)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示