D03【模板】最短路 Bellman-Ford 算法 SPFA 算法 P3385 负环

D03 最短路 Bellman-Ford 算法 SPFA 算法——信息学奥赛算法_哔哩哔哩_bilibili

 

最短路 - OI Wiki

队列优化:SPFA

即 Shortest Path Faster Algorithm.

很多时候我们并不需要那么多无用的松弛操作.

只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作.

那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了.

SPFA 也可以用于判断 𝑠 点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少 𝑛 条边时,说明 𝑠 点可以抵达一个负环.

虽然在大多数情况下 SPFA 跑得很快,但其最坏情况下的时间复杂度为 𝑂(𝑛𝑚),将其卡到这个复杂度也是不难的,所以考试时要谨慎使用

(在没有负权边时最好使用 Dijkstra 算法,在有负权边且题目中的图没有特殊性质时,若 SPFA 是标算的一部分,题目不应当给出 Bellman–Ford 算法无法通过的数据范围).

 

P3371 【模板】单源最短路径(弱化版) - 洛谷

// spfa算法 O(m~nm)
#include<bits/stdc++.h>
using namespace std;

const int N=10010,M=500010,inf=(1<<31)-1;
int n,m,s,a,b,c;
int h[N],to[M],w[M],ne[M],tot;
void add(int a,int b,int c){
  to[++tot]=b;w[tot]=c;ne[tot]=h[a];h[a]=tot;
}
int d[N],vis[N];

void spfa(){
  for(int i=1;i<=n;i++) d[i]=inf; d[s]=0;
  queue<int>q; q.push(s); vis[s]=1; //标记在队中
  while(q.size()){
    int u=q.front(); q.pop(); vis[u]=0; //标记不在队中
    for(int i=h[u];i;i=ne[i]){
      int v=to[i];
      if(d[v]>d[u]+w[i]){ //松弛
        d[v]=d[u]+w[i];
        if(!vis[v]) q.push(v),vis[v]=1; //不在队中才入队
      }
    }
  }
}
int main(){
  cin>>n>>m>>s;
  for(int i=0;i<m;i++){
    scanf("%d%d%d",&a,&b,&c);
    add(a,b,c);
  }
  spfa();
  for(int i=1;i<=n;i++) printf("%d ",d[i]);
}

 

P3385 【模板】负环 - 洛谷

//spfa 判负环 530ms
#include<bits/stdc++.h>
using namespace std;

const int N=2010,M=6010;
int n,m;
int h[N],to[M],ne[M],w[M],tot;
int d[N],vis[N],cnt[N];

void add(int a,int b,int c){
  to[++tot]=b;w[tot]=c;ne[tot]=h[a];h[a]=tot;
}
bool spfa(){ //判负环
  memset(d,0x3f,sizeof d); d[1]=0;
  memset(vis,0,sizeof vis);
  memset(cnt,0,sizeof cnt);
  
  queue<int>q; q.push(1); vis[1]=1; //在队中
  while(q.size()){
    int u=q.front();q.pop();vis[u]=0;
    for(int i=h[u];i;i=ne[i]){
      int v=to[i];
      if(d[v]>d[u]+w[i]){ //松弛
        d[v]=d[u]+w[i];
        if(!vis[v]) q.push(v),vis[v]=1;
        cnt[v]=cnt[u]+1; //边数
        if(cnt[v]>=n) return 1; //有负环   
      }
    }
  }
  return 0;
}
int main(){
  int T; scanf("%d",&T);
  while(T--){
    tot=0; memset(h,0,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
      int u,v,w;
      scanf("%d%d%d",&u,&v,&w);
      add(u,v,w);
      if(w>=0)add(v,u,w);;
    }
    puts(spfa()?"YES":"NO");
  }
}

 

//spfa 判负环 690ms
#include<bits/stdc++.h>
using namespace std;

const int inf=0x3f3f3f3f;
const int N=2010,M=6010;
int n,m;
int to[M],ne[M],w[M],h[N],tot;
int d[N],cnt[N],vis[N];

void add(int a,int b,int c){
  to[++tot]=b;w[tot]=c;
  ne[tot]=h[a];h[a]=tot;
}
bool spfa(){ //判负环
  memset(d,0x3f,sizeof d); 
  memset(vis,0,sizeof vis);
  memset(cnt,0,sizeof cnt);
  queue<int>q;
  q.push(1); vis[1]=1; d[1]=0; 
  while(q.size()){
    int u=q.front();q.pop();vis[u]=0;
    for(int i=h[u];i;i=ne[i]){
      int v=to[i];
      if(d[v]>d[u]+w[i]){
        d[v]=d[u]+w[i];
        if(++cnt[v]>n)return 1;//判点数
        if(!vis[v])q.push(v),vis[v]=1;
      }
    }
  }
  return 0;
}
int main(){
  int T; scanf("%d",&T);
  while(T--){
    tot=0; memset(h,0,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
      int u,v,w;
      scanf("%d%d%d",&u,&v,&w);
      add(u,v,w);
      if(w>=0)add(v,u,w);;
    }
    puts(spfa()?"YES":"NO");
  }
  return 0;
}

 

//Ford 判负环 740ms
#include<bits/stdc++.h>
using namespace std;

const int inf=0x3f3f3f3f;
const int N=2010,M=6010;
int n,m;
int h[N],to[M],w[M],ne[M],tot;
int d[N];

void add(int a,int b,int c){
  to[++tot]=b;w[tot]=c;
  ne[tot]=h[a];h[a]=tot;
}
bool ford(){
  memset(d,inf,sizeof d); d[1]=0;
  bool flag; //是否松弛
  for(int i=1;i<=n;i++){ //跑n轮
    flag=false;
    for(int u=1;u<=n;u++){ //n个点
      if(d[u]==inf)continue;
      for(int j=h[u];j;j=ne[j]){
        int v=to[j];
        if(d[v]>d[u]+w[j]){
          d[v]=d[u]+w[j];
          flag=true;
        }
      }
    }
    if(!flag)break;
  }
  return flag; //第n轮=true,有负环
}
int main(){
  int T; scanf("%d",&T);
  while(T--){
    tot=0; memset(h,0,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
      int u,v,w;
      scanf("%d%d%d",&u,&v,&w);
      add(u,v,w);
      if(w>=0)add(v,u,w);;
    }
    puts(ford()?"YES":"NO");
  }
}

 

posted @ 2022-05-28 13:09  董晓  阅读(2900)  评论(0)    收藏  举报