利用拓扑排序求 DAG 最短路

更多请见DUMBLOG

背景

给定一张带负权边的有向无环图(DAG), N N N 个点, M M M 条边,求源点到各点的最短路。

分析

因为有负权边,无法用 dijkstra 直接求最短路。

用 SPFA 又容易被出题人卡成傻子

考虑到是 DAG,可以使用拓扑排序 O ( N + M ) O\left( N+M \right) O(N+M) 时间范围内求出最短路。

步骤如下:

设源点为 s s s,点到源点的最短路用数组 d [ ] d[] d[] 来储存,建一个队列 q [ ] q[] q[] 给拓扑排序用,再建一个数组 d e g [ ] deg[] deg[] 用来存储每个点的入度。

初始化:将除了 d [ s ] d[s] d[s] 以外的所有 d [ ] d[] d[] 赋值为 + ∞ +\infty + d [ s ] d[s] d[s] 赋值为 0 0 0,队列 q [ ] q[] q[] 为空,提前处理出每个点的 d e g deg deg

一、找出所有入度为 0 0 0 的节点加入队列 q [ ] q[] q[]

二、取出队首节点,设队首节点为 p p p,该点指向的点为 j j j,两点间的边的边权为 w w w

三、将 d e g [ j ] deg[j] deg[j] 减一(删边),并用 d [ p ] + w d[p]+w d[p]+w 来更新 d [ j ] d[j] d[j]。若 d e g [ j ] deg[j] deg[j] 被减为零,则让 j j j 入队。

重复二到三,直到所有点的 d e g [ ] deg[] deg[] 都变成 0 0 0,即所有点都被访问过(此时队空)。

过程中所有的点和边都只被扫描了一次,时间复杂度为 O ( N + M ) O\left( N+M \right) O(N+M)

其实仔细看整个过程会发现这就是在拓扑排序模板上加了一小点东西,也是在 SPFA 模板上加了一小点东西

喜闻乐见,代码时间

#include<bits/stdc++.h>
const int N=114514;
using namespace std;
int n,m,s;
int h[N],nxt[N],to[N],w[N],cn;
int d[N];
int deg[N];
queue<int> q;
//变量名与前面一一对应
void add(int a,int b,int c){
	to[++cn]=b;
	w[cn]=c;
	nxt[cn]=h[a];
	h[a]=cn;
	deg[b]++;//在加边时顺便将 deg[] 处理出来
}
void topsort(int s){//拓扑排序,长得几乎和 SPFA 一模一样
	memset(d,0x3f,sizeof d);
	for(int i=1;i<=n;i++){
		if(!deg[i])q.push(i);
	}
	d[s]=0;
	while(q.size()){
		int p=q.front();
		q.pop();
		for(int i=h[p];i;i=nxt[i]){
			int j=to[i];
			d[j]=min(d[j],d[p]+w[i]);
			if(--deg[j]==0)q.push(j);
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	topsort(s);
	for(int i=1;i<=n;i++){
		printf("%d\n",d[i]);
	}
	return 0;
}

T h e   E n d {\Huge \mathfrak{The\ End}} The End

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值