SPFA 的优化
引
众所周知,SPFA作为一种暴力单源最短路算法,卡掉它并不是什么难事,毕竟,毕竟。
如果想卡 SPFA 见:「笔记」如何优雅地卡 Spfa - Luckyblock
但是我们可以优化……
但是优化也可以卡
SLF 优化
\(Small \ Label \ First\) 优化(小标签优先)。用双端队列 \(deque\) 实现,常用。
就是更新完之后比较其 \(dis\) 值与 \(dis[q.front()]\) 的大小,如果较小则放前面,反之放后面。
原理就是 \(dis\) 值小的更容易更新其他节点的 \(dis\) ,使队列后面的节点尽量不满足 \(dis[v]>dis[u]+w\),这样就避免了一部分的重复入队。
复杂度 \(\mathcal{O(\text{玄学})}\)。
本来是不准备写代码的,但是有人要求那我就放上别人的代码吧(((((
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<deque>
#define MAXN 10010
#define MAXM 500010
#define MAX 2147483647
using namespace std;
int n,m,s,t,c=1;
int head[MAXN],path[MAXN];
bool vis[MAXN];
struct node{
int next,to,w;
}a[MAXM<<1];
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
inline int relax(int u,int v,int w){
if(path[v]>path[u]+w){
path[v]=path[u]+w;
return 1;
}
return 0;
}
inline void add(int u,int v,int w){
a[c].to=v;a[c].w=w;a[c].next=head[u];head[u]=c++;
}
void spfa(){
int u,v;
deque<int> q;
for(int i=1;i<=n;i++){path[i]=MAX;vis[i]=false;}
path[s]=0;
vis[s]=true;
q.push_back(s);
while(!q.empty()){
u=q.front();
q.pop_front();
vis[u]=false;
for(int i=head[u];i;i=a[i].next){
v=a[i].to;
if(relax(u,v,a[i].w)&&!vis[v]){
vis[v]=true;
if(!q.empty()&&path[v]<path[q.front()])q.push_front(v);
else q.push_back(v);
}
}
}
for(int i=1;i<=n;i++)printf("%d ",path[i]);
printf("\n");
}
int main(){
int u,v,w;
n=read();m=read();s=read();
for(int i=1;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w);
}
spfa();
return 0;
}
LLL 优化
\(Large\ Label\ Last\) 优化(大标签最后)。
设队首元素为 \(temp\) ,每次松弛时进行判断,队列中所有 \(dis\) 值的和为 \(sum\),队列元素个数为 \(num\)。
若 \(dis[temp] \cdot num > sum\) ,则将 \(temp\) 取出插入到队尾,查找下一元素,直到找到某一个 \(temp\) 使得 \(dis[ temp ] \cdot sum \le x\) ,则将 \(temp\) 出队进行松弛操作。
和 SLF 优化原理差不多,因为比平均值小的 \(dis\) 更容易更新别的 \(dis\) 从而减少入队次数。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<list>
#define MAXN 10010
#define MAXM 500010
#define MAX 2147483647
using namespace std;
int n,m,s,t,c=1;
int head[MAXN],path[MAXN];
bool vis[MAXN];
struct node{
int next,to,w;
}a[MAXM<<1];
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
inline int relax(int u,int v,int w){
if(path[v]>path[u]+w){
path[v]=path[u]+w;
return 1;
}
return 0;
}
inline void add(int u,int v,int w){
a[c].to=v;a[c].w=w;a[c].next=head[u];head[u]=c++;
}
void spfa(){
int u,v,num=0;
long long x=0;
list<int> q;
for(int i=1;i<=n;i++){path[i]=MAX;vis[i]=false;}
path[s]=0;
vis[s]=true;
q.push_back(s);
num++;
while(!q.empty()){
u=q.front();
q.pop_front();
num--;x-=path[u];
while(num&&path[u]>x/num){
q.push_back(u);
u=q.front();
q.pop_front();
}
vis[u]=false;
for(int i=head[u];i;i=a[i].next){
v=a[i].to;
if(relax(u,v,a[i].w)&&!vis[v]){
vis[v]=true;
if(!q.empty()&&path[v]<path[q.front()])q.push_front(v);
else q.push_back(v);
num++;x+=path[v];
}
}
}
for(int i=1;i<=n;i++)printf("%d ",path[i]);
printf("\n");
}
int main(){
int u,v,w;
n=read();m=read();s=read();
for(int i=1;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w);
}
spfa();
return 0;
}
SLF + LLL 优化
因为这俩玩意互不影响,一个判出队,一个判入队,结合起来一样用。
DFS优化
这玩意可以判环,因为依靠队列判环至少需要一个点重复进队 \(n\) 次,所以极易 GG。
DFS 优化可以凭借一个 \(vis\) 数组将判环的复杂度降到到 \(\mathcal{O(\text{E})}\)
蛋是,要是没环,见 LB 如何卡 SPFA 。
复杂度指数级。
bool dfs_SPFA(int u){
vis[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]<dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(vis[v]) return true;
else if(dfs_SPFA(v)) return true;
}
}
vis[u]=false;
return false;
}