专栏文章

题解:P4779 【模板】单源最短路径(标准版)

P4779题解参与者 7已保存评论 9

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
9 条
当前快照
1 份
快照标识符
@miouk19q
此快照首次捕获于
2025/12/03 01:23
3 个月前
此快照最后确认于
2025/12/03 01:23
3 个月前
查看原文
这道题很显然是 Dijkstra 的板子题

题目描述

给定一张有向图,让你求出从起点 ss 出发,到每个点的最短距离。

算法

这道题应该使用堆优化的 Dijkstra 吧,如果不会没有堆优化的 Dijkstra 的话,请转至 P3371
Dijkstra 一定是在没有负边权的图上使用,具体的后面讲。

定义部分

毕竟是堆优化,我们肯定要定义一个堆,就是priority_queue,他叫优先队列。他就是一个二叉堆。但是只定义一个可不行,我们需要建立一个结构体,里面是节点编号以及到从 ss 出发到这个点的最短距离。
CPP
struct node{
	long long id, dis;
};
bool operator <(const node &x, const node &y){
    return y.dis<x.dis;//排序
}
priority_queue<node> q;
还有一个 vector,这个就很常规了。
CPP
struct E{
	long long v, w;
};
vector<E> e[N];
然后是一个记录距离,也就是答案的数组 dis
其他的就是题目里要求的啦。

算法步骤

主要思想就是,我们找当前节点的所有出边,如果找到一个比当前答案更优的答案,就改变当前答案。
流程:
  1. 初始化 dis[s] 为零,其他 dis 为无穷大。
  2. 在为被标记的点中找到 dis[x] 最小的 xx,并标记结点 xx
  3. 扫描 xx 的所有出边 (x,y,z)(x,y,z),如果 dis(y)>dis(x)+zdis(y) > dis(x) + z,则更新 dis(y)dis(y)
  4. 重复步骤二到三直至所有结点被标记。
以下图为例。
为了方便,我们用字母表示边的编号,括号内的数字表示边权。
ss 为一为例,前面初始化省去。
  1. aa,边权为一,显然小于节点二的答案,更新节点二的答案。
  2. bb,边权为一,更新节点三答案。
  3. 遍历到了节点二,看 cc,边权为三,更新节点四的答案。
  4. 遍历到了节点三,看 dd,边权为二,更新节点五的答案。
  5. 遍历到了节点四,看 ee,边权为四,不更新节点五的答案。
这样,我们就把这张图遍历完了。
但是,上述过程并没有用到堆优化,所以时间复杂度为 O(n2)O(n ^ 2)
事实上,其时间复杂度瓶颈为寻找结点 xx,这个过程可以用优先队列 priority_queue 优化。 优化后均摊时间复杂度为 𝑂((n+m)logn)𝑂((n + m) \log n)
还记得前文中说 Dijkstra 不能处理负边权吗,这里解释一下。
其思想基于贪心:当边长为非负数,全局最小值必然不可 能再被更新。
即,每次在第二步中取出的 xxdis(x)dis(x) 已经是起点到 xx 的最短路径。
标记是因为,dis(x)dis(x) 只能更新别的结点一次,保证时间复杂度。
好了,该讲的都讲完了,现在上代码。
CPP
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long n, m, s;
long long u, v, w;
long long dis[N];
struct E{
	long long v, w;//边的终点和权值
};
struct node{
	long long id, dis;//节点编号和距离
};
bool operator <(const node &x, const node &y){
    return y.dis<x.dis;//优先队列按距离从大到小排序
}
vector<E> e[N];
priority_queue<node> q;
void Dijkstra(int s){
	for(int i=1;i<=n;i++)
		dis[i]=1e18;//初始化距离
	dis[s]=0;//源点距离为0
	q.push({s,0});//入队
	while(!q.empty()){
		node now=q.top();//取出队首元素
		q.pop();//弹出队顶
		int u=now.id;//当前节点
		if(now.dis>dis[u]) continue;//小优化
		for(auto v:e[u]){//遍历当前节点的出边
			if(dis[v.v]>dis[u]+v.w){//更新距离
				dis[v.v]=dis[u]+v.w;///更新距离
				q.push({v.v, dis[v.v]});//入队
			}
		}
	}
	return;
}
int main(){
	cin>>n>>m>>s;
	for(int i=1;i<=m;i++){
		cin>>u>>v>>w;
		e[u].push_back({v,w});//建边
	}
	Dijkstra(s);//计算最短路
	for(int i=1;i<=n;i++)//输出答案
		cout<<dis[i]<<" ";
	return 0;//完结撒花
}

结尾

这篇题解写的可能有点乱,希望大家见谅。

评论

9 条评论,欢迎与作者交流。

正在加载评论...