题目链接:https://www.luogu.org/problemnew/show/P4779
题目背景
2018 年 7 月 19 日,某位同学在 NOI Day 1 T1 归程 一题里非常熟练地使用了一个广为人知的算法求最短路。
然后呢?
100→60;
Ag→Cu;
最终,他因此没能与理想的大学达成契约。
小 F 衷心祝愿大家不再重蹈覆辙。
题目描述
给定一个 N 个点,M 条有向边的带非负权图,请你计算从 S 出发,到每个点的距离。
数据保证你能从 S 出发到任意点。
输入格式:
第一行为三个正整数 N,M,S。 第二行起 M 行,每行三个非负整数 ui,vi,wi,表示从 ui 到 vi 有一条权值为 wi 的边。
输出格式:
输出一行 N 个空格分隔的非负整数,表示 S 到每个点的距离。
4 6 1 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4
题解:
这是一道最短路的模板题,但是它卡SPFA,还卡某些优化不好的SPFA,所以本题我们要使用SLF+swap优化的SPFA。
(顺便再用堆优化dijkstra)
首先回顾一下Bellman-Ford算法:
①初始化,所有点的 dist[i] = INF,出发点 s 的dist[s] = 0;
②对于每条边 edge(u,v),若 dist[u] != INF,且dist[v] > dist[u] + edge(u,v).w,则松弛dist[v] = dist[u] + edge(u,v).w
③循环步骤② $\left| V \right| - 1$ 次,或者知道某一次步骤②中没有边可以松弛,则转步骤④
④若存在一条边 edge(u,v),满足 dist[u] != INF,且dist[v] > dist[u] + edge(u,v).w,则图中存在负环。
我们知道,Bellman-Ford算法的时间复杂度是 $O\left( {\left| V \right|\left| E \right|} \right)$,而我们可以使用队列对其进行优化,那就是大名鼎鼎的SPFA算法,
所以说,SPFA就是队列优化的Bellman-Ford算法。
不妨回顾一下SPFA算法:
①初始化,所有点的 dist[i] = INF,源点 s 的dist[s] = 0;构建队列,源点 s 入队,并标记该点已在队列中。
②队头出队,标记该点已不在队列中(若图存在负权边,则可以对该点出队次数检查,若出队次数大于 n,则存在负环,算法结束),
遍历该点出发的所有边,假设当前遍历到某条边为 edge(u,v),若 dist[v] > dist[u] + edge(u,v).w,则松弛dist[v] = dist[u] + edge(u,v).w,
检查节点 v 是否在队列中,若不在则入队,标记节点 v 已在队列中。
④重复执行步骤②直到队列为空。
但是,对于这个“队列优化”,有必要清楚的一点是:
SPFA的时间复杂度,其实和Bellman-Ford是一样的,都是$O\left( {\left| V \right|\left| E \right|} \right)$,
只是SPFA在部分图中跑的比较快,给人以 $O\left( {k\left| E \right|} \right)$ 的感觉(其中 $k$ 为所有点入队次数的平均,部分图的 $k$ 值很小),
但是,现在很多的题目,都是会卡掉SPFA的。所以,现在对于没有负权边的图,单源最短路请优先考虑堆优化Dij
当然啦,SPFA被卡了我还是想用SPFA怎么办?根据知乎上@fstqwq对于“如何看待SPFA算法已死这种说法?”的回答表明,
在不断的构造图卡SPFA和不断地优化SPFA过数据的斗争中,LLL优化、SLF优化、SLF带容错等一系列优化都被卡掉了,
而到目前(2018.9.4)为止,有位神仙想出了一种SLF+swap优化的SPFA,暂时还很难卡掉,心向往之情不自禁地就想了解一下:
首先是单纯的 SLF优化:Small Label First策略,设要入队的节点是 j,而队首元素为 i,若dist[j] < dist[i] 则将 j 插入队首,否则插入队尾。
再然后是 SLF+swap优化:每当队列改变时,如果队首节点 i 的 dist[i] 大于队尾节点 j 的 dist[j],则交换首尾节点。
SLF+swap优化的AC代码:
#include<bits/stdc++.h> using namespace std; typedef pair<int, int> pii; const int maxn=1e5+10; const int INF=0x3f3f3f3f; int n,m,s; //邻接表存图 struct Edge{ int u,v,w; Edge(int u=0,int v=0,int w=0){this->u=u,this->v=v,this->w=w;} }; vector<Edge> E; vector<int> G[maxn]; void addedge(int u,int v,int w) { E.push_back(Edge(u,v,w)); G[u].push_back(E.size()-1); } //数组模拟队列 const int Qsize=2e5+10; int head,tail; int Q[Qsize]; //SPFA单源最短路 int dist[maxn]; bool vis[maxn]; void spfa() { for(int i=1;i<=n;i++) dist[i]=INF,vis[i]=0; dist[s]=0; head=tail=0; Q[tail++]=s; vis[s]=1; while(head<tail) { int u=Q[head++]; vis[u]=0; if(head<tail-1 && dist[Q[head]]>dist[Q[tail-1]]) swap(Q[head],Q[tail-1]); for(int i=0;i<G[u].size();i++) { Edge &e=E[G[u][i]]; int v=e.v; if(dist[v]>dist[u]+e.w) { dist[v]=dist[u]+e.w; if(!vis[v]) { Q[tail++]=v; vis[v]=1; if(head<tail-1 && dist[Q[head]]>dist[Q[tail-1]]) swap(Q[head],Q[tail-1]); } } } } } int main() { scanf("%d%d%d",&n,&m,&s); for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); } spfa(); for(int i=1;i<=n;i++) printf("%d%s",dist[i],((i==n)?"\n":" ")); }