代码随想录-训练营-day51

今天我们来专注贝尔曼福德算法,考虑他的优化问题。

94. 城市间货物运输 I (kamacoder.com)

依然是这个问题,我们来看如何实现一个改善:

#include<iostream>
#include<vector>
#include<climits>
#include<queue>
#include<list>
using namespace std;
struct Edge{
    int to;
    int val;
    Edge(int t,int v):to(t),val(v){}
};
int main(){
    int n,m,s,t,v;
    cin>>n>>m;
    vector<list<Edge>> grid(n+1);
    while(m--){
        cin>>s>>t>>v;
        grid[s].push_back({t,v});
    }
    vector<int> minDist(n+1,INT_MAX);
    vector<bool> isInQueue(n+1,false);
    queue<int> q;
    q.push(1);
    minDist[1]=0;
    isInQueue[1]=true;
    while(!q.empty()){
        int node=q.front();
        q.pop();
        isInQueue[node]=false;
        for(Edge edge:grid[node]){
            int from=node;
            int to=edge.to;
            int val=edge.val;
            if(minDist[from]+val<minDist[to]){
                minDist[to]=minDist[from]+val;
                if(isInQueue[to]==false){
                    isInQueue[to]=true;
                    q.push(to);
                }
            }
        }
    }
    if(minDist[n]==INT_MAX)cout<<"unconnected"<<endl;
    else cout<<minDist[n]<<endl;
    return 0;
}

核心的做法是没有太多变化的,最明显的区别就在于我们:一是用邻接表来构建图,二是使用了一个队列和一个bool数组记录了每个点相邻的点,这样我们可以每次只松弛遍历的点的相连的边,也就是把一些无用的松弛操作给取消了。

和之前的迪杰斯特拉的算法一样,这种优化是依赖于图的一个结构的,对于稀疏图来说这样的优化非常有效,但对于稠密图来说就差别不大。

95. 城市间货物运输 II (kamacoder.com)

这个题提出了新的挑战:图中存在负权回路,也就是一系列的道路总权值为负。

显然针对这一系列问题,贪心算法就不用想了:他会陷入负权值的回路无限循环。让我们回味贝尔曼福德算法的核心思路,他会针对总计n个点n-1条边实现一个松弛:其实就是挨个点地更新该点到其他点的最小距离,假如道路中出现负权回路,我们的算法会出现一个什么情况呢?其实不难理解,按理说我们的算法正常情况下经过n-1次松弛之后,再怎么松弛我们的最小距离数组也不会进行更新了,但是出现负权值的回路时他依然会去更新,我们只需要在完成n-1次松弛之后再进行一次松弛判断最小距离数组是否发生变化即可。

#include<iostream>
#include<vector>
#include<climits>
#include<list>
#include<queue>
using namespace std;
struct Edge{
    int to,val;
    Edge(int t,int v):to(t),val(v){}
};
int main(){
    int n,m,s,t,v;
    cin>>n>>m;
    vector<list<Edge>> grid(n+1);
    while(m--){
        cin>>s>>t>>v;
        grid[s].push_back({t,v});
    }
    vector<int> minDist(n+1,INT_MAX);
    vector<bool> isInQueue(n+1,false);
    vector<int> count(n+1,0);
    bool flag=false;
    queue<int> q;
    q.push(1);
    minDist[1]=0;
    isInQueue[1]=true;
    while(!q.empty()){
        int node=q.front();
        q.pop();
        isInQueue[node]=false;
        for(Edge edge:grid[node]){
            int from=node;
            int to=edge.to;
            int val=edge.val;
            if(minDist[to]>minDist[from]+val){
                minDist[to]=minDist[from]+val;
                if(!isInQueue[to]){
                    isInQueue[to]=true;
                    q.push(to);
                    count[to]++;
                    if(count[to]==n){
                        flag=true;
                        while(!q.empty())q.pop();
                        break;
                    }
                }
            }
        }
    }
    if(flag)cout<<"circle"<<endl;
    else if(minDist[n]==INT_MAX)cout<<"unconnected"<<endl;
    else cout<<minDist[n]<<endl;
    return 0;
}

上述是优化过的spfa算法,我们也可以用基础的贝尔曼福德算法来实现这个过程:

#include<iostream>
#include<vector>
#include<climits>
using namespace std;

int main(){
    int n,m,s,t,v;
    cin>>n>>m;
    vector<vector<int>> grid;
    while(m--){
        cin>>s>>t>>v;
        grid.push_back({s,t,v});
    }
    vector<int> minDist(n+1,INT_MAX);
    bool flag=false;
    minDist[1]=0;
    for(int i=1;i<=n;++i){
        for(vector<int> &edge:grid){
            int from=edge[0];
            int to=edge[1];
            int val=edge[2];
            if(i<n){
                if(minDist[from]!=INT_MAX&&minDist[to]>minDist[from]+val){
                    minDist[to]=minDist[from]+val;
                }
            }
            else{
                if(minDist[from]!=INT_MAX&&minDist[to]>minDist[from]+val){
                    minDist[to]=minDist[from]+val;
                    flag=true;
                }
            }

        }
    }
    if(flag)cout<<"circle"<<endl;
    else if(minDist[n]==INT_MAX)cout<<"unconnected"<<endl;
    else cout<<minDist[n]<<endl;
    return 0;
}

96. 城市间货物运输 III (kamacoder.com)

这个题在存在负权回路的基础上,还有一个设定是我们的松弛次数是有上限的。

之前我们对于总计N个点,需要松弛N-1次,因为松弛的本质就是去更新起点到终点的权值,而现在我们最多经过k个城市,那么需要松弛多少次呢?

答案是k+1次:

那我们想到的第一步就是把之前的贝尔曼福德算法的松弛次数从n-1修改为k+1即可。

但是这里涉及到一个贝尔曼福德的性质:

对于有负权回路存在的图,我们的贝尔曼福德算法松弛时遍历的顺序不同会导致结果不同。

这时候我们需要去获得上一次minDist更新的结果,保证在上一次更新的基础上继续更新。

#include<iostream>
#include<vector>
#include<climits>
using namespace std;
int main(){
    int n,m,s,t,v;
    cin>>n>>m;
    vector<vector<int>> grid;
    while(m--){
        cin>>s>>t>>v;
        grid.push_back({s,t,v});
    }
    int src,dst,k;
    cin>>src>>dst>>k;
    vector<int> minDist(n+1,INT_MAX);
    vector<int> minDist_copy(n+1);
    minDist[src]=0;
    for(int i=1;i<=k+1;++i){
        minDist_copy=minDist;
        for(vector<int>& edge:grid){
            int from=edge[0];
            int to=edge[1];
            int val=edge[2];
            if(minDist_copy[from]!=INT_MAX&&minDist[to]>minDist_copy[from]+val){
                minDist[to]=minDist_copy[from]+val;
            }
        }
    }
    if(minDist[dst]==INT_MAX)cout<<"unreachable"<<endl;
    else cout<<minDist[dst]<<endl;
    return 0;
}