Y1课程笔记:第八课时(bellmanford算法,SPFA算法,floyd算法)

文章介绍了几种常见的最短路算法,包括Dijkstra、堆优化Dijkstra、Bellman-Ford、SPFA以及多源汇最短路的Floyd算法。在负权图中,Bellman-Ford和SPFA用于解决最短路问题,而Floyd算法适用于多源汇的最短路径。此外,文章还提供了AC代码示例和相关题目解析,帮助读者理解这些算法的应用。
摘要由CSDN通过智能技术生成

先上此图

最短路算法总结: 

单源最短路{ 

                非负权图{

                        dijkstra_(n*n)

                        堆优化dijkstra_o(n+mlogn)

                }

                存在负权图{

                        Bell-ford_o(n*m)

                        SPFA _平均o(m),最坏o(n*m)

                }

        }

分类2

        多源汇最短路{

                floyd_o(n^3)

        }

知识点一:bellmanford算法&SPFA算法

SPFA算法实际上是bellmanford算法的队列优化版本

时间复杂度:  

        Bell-ford  O(n*m)

        SPFA   平均o(m),最坏o(n*m)

由此可见,SPFA算法比bellmanford算法更为重要

先上两个模板题

最短路

时间限制:1秒        内存限制:32M

题目描述

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

输入描述

输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。输入保证至少存在1条商店到赛场的路线。
当输入为两个0时,输入结束。

输出描述

对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。

样例

输入

3 3
1 2 1
2 3 1
1 3 3
3 3
1 2 2
2 3 1
1 3 1
0 0

输出

2
1

AC代码

SPFA版本

#include<bits/stdc++.h>
using namespace std;
int n,m,dis[1005];
int he[20005],ne[20005],w[20005],ver[20005],tot=-1,vis[20005];
void add(int a,int b,int c){
	tot++;
	ver[tot]=b;
	w[tot]=c;
	ne[tot]=he[a];
	he[a]=tot;
} 
struct node{
    int x,y,z;
}a[10005];
void bf(int s){
    memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
    dis[s]=0;
    vis[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        //cout<<u<<endl;
        for(int j=he[u];~j;j=ne[j]){
            //cout<<ver[j]<<' '<<w[j]<<endl;
            int v=ver[j],wn=w[j];
            //cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
            if(dis[v]>dis[u]+wn){
                //cout<<"che1"<<endl;
                dis[v]=dis[u]+wn;
                if(vis[v]==0){
                    //cout<<"che2"<<endl;
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    /*for(int j=1;j<=m;j++){
        int x=a[j].x,y=a[j].y,z=a[j].z;
        if(dis[y]>cp[x]+z){
            flag=1;
        }
    }*/
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    while(cin>>n>>m&&n){
    memset(he,-1,sizeof he);
    memset(w,0,sizeof w);
    memset(ver,0,sizeof ver);
    memset(ne,0,sizeof ne);
	int x,y,z;
	for(int i=1;i<=m;i++){
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,z);
	}
	bf(1);
    cout<<dis[n]<<endl;
}
}

bf版本

void bf(int s){
    for(int j=1;j<=n;j++){
        cp[j]=0x3f3f3f3f;
        dis[j]=0x3f3f3f3f;
    }
	memset(st,0,sizeof st);
    dis[s]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cp[j]=dis[j];
		}
        for(int j=1;j<=m;j++){
            int x=a[j].x,y=a[j].y,z=a[j].z;
            if(dis[y]>cp[x]+z){
                dis[y]=min(dis[y],cp[x]+z);
            }
            //cout<<dis[j]<<' ';
        }
    }
    if(dis[n]>(0x3f3f3f3f/2)){
        flag=1;
    }
}

spfa判断负环

时间限制:1秒        内存限制:128M

题目描述

给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。

请你判断图中是否存在负权回路。

输入描述

第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z:表示存在一条从点x到点y的有向边,边长为z。

输出描述

如果图中存在负权回路, 则输出”Yes”, 否则输出”No”。

样例

输入

3 3
1 2 -1
2 3 4
3 1 -4

输出

Yes

提示

1≤n≤2000
1≤m≤10000,
图中涉及边长绝对值均不超过10000。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,dis[2005];
int minn[2005][2005],cnt[2005],he[20005],ne[20005],w[20005],ver[20005],tot=-1,vis[20005];
void add(int a,int b,int c){
	tot++;
	ver[tot]=b;
	w[tot]=c;
	ne[tot]=he[a];
	he[a]=tot;
}

struct node{
    int x,y,z;
}a[10005];
bool bf(int s){
    memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	memset(cnt,0,sizeof cnt);
    dis[s]=0;
    vis[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        //cout<<u<<endl;
        for(int j=he[u];~j;j=ne[j]){
            //cout<<ver[j]<<' '<<w[j]<<endl;
            int v=ver[j],wn=w[j];
            //cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
            if(dis[v]>dis[u]+wn){
                //cout<<"che1"<<endl;
                dis[v]=dis[u]+wn;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n){
                    return true; 
                }
                if(vis[v]==0){
                    //cout<<"che2"<<endl;
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    /*for(int j=1;j<=m;j++){
        int x=a[j].x,y=a[j].y,z=a[j].z;
        if(dis[y]>cp[x]+z){
            flag=1;
        }
    }*/
    return false;
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    while(cin>>n>>m&&n){
    memset(he,-1,sizeof he);
    memset(w,0,sizeof w);
    memset(ver,0,sizeof ver);
    memset(ne,0,sizeof ne);
    memset(minn,0x3f,sizeof minn);
	int x,y,z;
	for(int i=1;i<=m;i++){
		cin>>x>>y>>z;
		if(x==y){
		    if(z<0){
		    cout<<"Yes";
	        return 0;
		    }
		    else{
		        continue;
		    }
		}
		if(z<minn[x][y]){
		    add(x,y,z);
		}
	}
	for(int i=1;i<=n;i++){
	    if(bf(i)){
	        cout<<"Yes";
	        return 0;
	    }
	}
    cout<<"No";
}
}

习题

赚钱

时间限制:1秒        内存限制:128M

题目描述

kdy现在决定环游中国,顺便赚点钱。kdy在一个城市最多只能赚D元,然后他可以选择退休也就是停止赚钱,或者去其它城市工作。当然,他可以在别处工作一阵子后又回到原来的城市再赚D元。这样的往返次数是没有任何限制的。城市间有P条单向路径连接,共有C座城市,编号从1到C。路径i从城市Ai到城市Bi,在路径行走上不用任何花费。kdy还可以乘飞机从某个城市飞到另一个城市。共有F条单向的航线,第i条航线是从城市Ji飞到另一座城市Ki,费用是Ti元。假如kdy身上没有现钱,他可以用以后赚的钱来付机票钱。kdy可以从任何一个城市出发开始赚钱,并且选择在任何时候、任何城市退休。现在kdy想要知道,如果在工作时间上不做限制,那么kdy共可以赚多少钱呢?如果赚的钱也不会出现限制,那么就输出orz。  

输入描述

  第一行,4个用空格分开的正整数,D,P,C,F。第二行到P+1行,第i+1行包含2个用空格分开的整数,表示一条从城市Ai到城市Bi的单向路径。接下来的F行,每行3个用空格分开的正整数,表示一条从城市Ji到城市Ki的单向航线,费用为Ti。  

对于100%的数据,1<=D<=1000,1<=P<=200,2<=C<=300,1<=F<=400。

输出描述

  如果kdy赚的钱没有限制,输出orz。如果有限制,那么就输出在给定的规则下kdy最多可以赚到的钱数。  

样例

输入

100 3 5 2
1 5
2 3
1 4
5 2 150
2 5 120

输出

250

解题思路

这道题乍一看很复杂,要求繁多,但在宏观上,我们可以简化一下题目:

将最多赚到的钱当做为每条小路的权,用每条航线的权减去此航线上的机票价格,最终以每座城市为起点进行SPFA求最大路径即可。

接下来,我们尝试解决细节上的问题:

因为走小路不要钱,所以当有小路组成环时,便可以无限赚钱,因此我们可以通过判断正环来解决此问题。

BUT:

无论是此题目中的SPFA求最大值,还是判正环,都是我们所不熟悉的,那为何不逆向思维一下,将边权以相反数存储,最终求最小值和负环。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,d,p,f,dis[305],flag;
int he[2000005],ne[2000005],w[2000005],ver[2000005],tot=-1,vis[305],cnt[305];
void add(int a,int b,int c){
	tot++;
	ver[tot]=b;
	w[tot]=c;
	ne[tot]=he[a];
	he[a]=tot;
}
int bf(int s){
    memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	memset(cnt,0,sizeof cnt);
    dis[s]=-d;
    vis[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        //cout<<u<<endl;
        for(int j=he[u];~j;j=ne[j]){
            //cout<<ver[j]<<' '<<w[j]<<endl;
            int v=ver[j],wn=w[j];
            //cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
            if(dis[v]>dis[u]+wn){
                //cout<<"che1"<<endl;
                dis[v]=dis[u]+wn;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n){
                    flag=1; 
                    return 0x3f3f3f3f;
                }
                if(vis[v]==0){
                    //cout<<"che2"<<endl;
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    /*for(int j=1;j<=m;j++){
        int x=a[j].x,y=a[j].y,z=a[j].z;
        if(dis[y]>cp[x]+z){
            flag=1;
        }
    }*/
    return 0x3f3f3f3f;
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    cin>>d>>p>>n>>f;
    memset(he,-1,sizeof he);
    memset(w,0,sizeof w);
    memset(ver,0,sizeof ver);
    memset(ne,0,sizeof ne);
	int x,y,z;
	for(int i=1;i<=p;i++){
		cin>>x>>y;
		add(x,y,-d);
	}
	for(int i=1;i<=f;i++){
		cin>>x>>y>>z;
		add(x,y,z-d);
	}
	int anss=0x3f3f3f3f;
	for(int i=1;i<=n;i++){
	    flag=0;
	    bf(i);
	    if(flag==1){
	        cout<<"orz";
	        return 0;
	    }
	    //cout<<i<<endl;
	    for(int i=1;i<=n;i++){
	        //cout<<dis[i]<<' ';
	        anss=min(anss,dis[i]);
	    }
	}
	cout<<-anss;
}

知识点二:Floyd算法

模板题

Floyd求最短路

时间限制:1秒        内存限制:128M

题目描述

给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定k个询问,每个询问包含两个整数x和y:表示查询从点x到点y的最短距离,如果路径不存在,则输出”impossible”。
数据保证图中不存在负权回路。

输入描述

第一行包含三个整数n,m,k
接下来m行,每行包含三个整数x,y,Z:表示存在一条从点x到点y的有向边,边长为z。
接下来k行,每行包含两个整数x,y,表示询问点x到点y的最短距离。

输出描述

共k行, 每行输出一个整数, 表示询问的结果, 若询问两点间不存在路径, 则输出”impossible”。

样例

输入

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出

impossible
1

提示

1≤n<=200,
1<=k<=n^2
1≤m≤20000
图中涉及边长绝对值均不超过10000。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,k,g[205][205];
void flo(){
    for(int o=1;o<=n;o++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j]=min(g[i][j],g[i][o]+g[o][j]);
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(g,0x3f3f3f3f,sizeof g);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
	    g[i][i]=0;
	}
    int xx,yy,zz;
    for(int i=1;i<=m;i++){
	    cin>>xx>>yy>>zz;
	    g[xx][yy]=min(zz,g[xx][yy]);
	}
    flo();
    int q1,q2;
    for(int i=1;i<=k;i++){
        cin>>q1>>q2;
        if(g[q1][q2]>(0x3f3f3f3f/2)){
            cout<<"impossible"<<'\n';
        }
        else{
            cout<<g[q1][q2]<<'\n';
        }
    }
}

习题

道路重建

时间限制:1秒        内存限制:128M

题目描述

  从前,在一个王国中,在n个城市间有m条道路连接,而且任意两个城市之间至多有一条道路直接相连。在经过一次严重的战争之后,有d条道路被破坏了。国王想要修复国家的道路系统,现在有两个重要城市A和B之间的交通中断,国王希望尽快的恢复两个城市之间的连接。你的任务就是修复一些道路使A与B之间的连接恢复,并要求修复的道路长度最小。  

输入描述

  输入文件第一行为一个整数n(2<n≤100),表示城市的个数。这些城市编号从1到n。第二行为一个整数m(n−1≤m≤n(n−1)/2),表示道路的数目。接下来的m行,每行3个整数i,j,k(1≤i,j≤n,i≠j,0<k≤100),表示城市i与j之间有一条长为k的道路相连。接下来一行为一个整数d(1≤d≤m),表示战后被破坏的道路的数目。在接下来的d行中,每行两个整数i和j,表示城市i与j之间直接相连的道路被破坏。最后一行为两个整数A和B,代表需要恢复交通的两个重要城市。  

输出描述

  输出文件仅一个整数,表示恢复A与B间的交通需要修复的道路总长度的最小值。  

样例

输入

3
2
1 2 1
2 3 2
1
1 2
1 3

输出

1

解题思路

在Floyd的基础上,先将正常的道路权设为零,需要维修路的权设为长度即可

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,k,g[205][205];
void flo(){
    for(int o=1;o<=n;o++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j]=min(g[i][j],g[i][o]+g[o][j]);
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(g,0x3f3f3f3f,sizeof g);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
	    g[i][i]=0;
	}
    int xx,yy,zz;
    for(int i=1;i<=m;i++){
	    cin>>xx>>yy>>zz;
	    g[xx][yy]=min(-zz,g[xx][yy]);
	}
    cin>>k;
    int q1,q2;
    for(int i=1;i<=k;i++){
        cin>>q1>>q2;
        g[q1][q2]=-g[q1][q2];
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
	        if(g[i][j]<=0){
	            g[i][j]=0;
	        }
        }
	}
    flo();
    int mp1,mp2;
    cin>>mp1>>mp2;
    cout<<g[mp1][mp2];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值