图论基础之最短路和最小生成树

一、最短路

1.基础知识

a.Dijkstra算法:基于贪心。具体算法见蓝书P350。但是我个人更习惯从优先队列的bfs角度来理解。所以Dijkstra算法具有两个性质:1.每个点可能被更新多次,但是只能被取出扩展一次。2.当某个点第一次出队时,就已经找到了起点到它的最短路径。

b.Bellman-Ford算法与SPFA算法:Bellman-Ford算法基于迭代思想,而SPFA算法是在Bellman-Ford算法的基础上加入队列优化,可认为算法思想基于bfs。具体可见蓝书P353。

c.Floyd算法:基于动态规划,有方程F(k,i,j)=min(F(k-1,i,j),F(k-1,i,k)+F(k-1,k,j)) (F(k,i,j)表示只经过前k个点中的若干个点,i与j之间的最短路径)。这里着重强调一下简化的方程F(i,j)=min(F(i,j),F(i,k)+F(k,j))。这里的关键在于弄明白为什么F(i,k)与F(k,j)仍处于第k-1层的状态。F(i,k)若被更新,显然有F(i,k)=min(F(i,k),F(i,k)+F(k,k)),其中F(k,k)=0,相当于没有更新,仍处于第k-1层。所以这个方程的正确性得到了严谨的证明。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=210;
int d[N][N],INF=1e9;
int m,n,Q;
void floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    cin>>n>>m>>Q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) d[i][j]=0;
            //注意这类涉及两点之间距离的存储需要把d[i][i]置为0
            else d[i][j]=INF;
        }
    }
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        d[a][b]=min(d[a][b],c);
    }
    floyd();
    while(Q--){
        int a,b;
        cin>>a>>b;
        if(d[a][b]>INF/2) cout<<"impossible"<<endl;
        //别忘了玄学判断
        else cout<<d[a][b]<<endl;
    }
}

2.例题

例题1:AcWing 340.通信线路(堆优化的Dijkstra算法)

这题看到“使得第K+1贵的电缆最便宜”一类的字样,就要考虑二分答案L。我们又发现:答案具有明显的单调性:当二分的值L增大时,最终其排名K就要减小;反之亦然。所以这题采用二分答案,并每次将图转化为无权图(0/1边权图)处理即可。即每次将大于L的边权置为1,其它置为0,求新图的最短路,dist[n]就是边权L的排名,根据排名来确定二分的取值变化。

代码如下:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1005,M=20*N;
int n,m,k;
int head[N],ne[M],to[M],w[M],dist[N],st[N],idx;
void add(int x,int y,int z){
    ne[++idx]=head[x];
    head[x]=idx;
    to[idx]=y;
    w[idx]=z;
}
bool spfa(int mid){
    queue<int> q;
    q.push(1);
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    st[1]=true;
    while(q.size()){
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=head[t];i;i=ne[i]){
            int j=to[i],s;
            if(w[i]>mid) s=dist[t]+1;
            else s=dist[t];
            if(s<dist[j]){
                dist[j]=s;
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    if(dist[n]<=k) return true;
    return false;
}
int main(){
    cin>>n>>m>>k;
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        add(a,b,w);
        add(b,a,w);
    }
    int l=0,r=1000000;
    while(l<r){
        int mid=(l+r)>>1;
        if(spfa(mid)){
            r=mid;
        }
        else{
            l=mid+1;
        }
    }
    if(r==1000000) cout<<-1<<endl;
    else cout<<r<<endl;
    return 0;
}

例题2:AcWing 341.最优贸易(SPFA算法)

这题题意一句话概括:求一条1~n的路径,使得该路径上的节点权值的最大值与最小值之差最大(且最大权值节点出现在最小权值节点之后,若路径存在环,也要满足路径序列上存在这样的两个点)。考虑到括号内的条件,想到用在原图上以1为起点,对所有点求一个dmin,即以该点为终点的,且路径上的节点权值的最小值最小。再在反图上以n为起点,对所有点求一个dmax,即以改点为终点的,且路径上的节点权值的最大值最大。那么最终求答案时,求最大的dmax[x]-dmin[x]即可。求法与求最短路相近,但是不能用Dijkstra算法。因为Dijkstra算法是在节点第一次出队时就取得最优值,而这题显然不满足这个条件,因为路径可能存在环,所以环上完全可能存在更小权值的节点去更新答案。所以这题显然应当采用SPFA算法。代码如下:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+5,M=2e6+5;
int h[N],rh[N],ne[M],to[M],idx;
int dmax[N],dmin[N],p[N];
bool st[N];
queue<int> q;
void add(int h[],int x,int y){
    ne[++idx]=h[x];
    to[idx]=y;
    h[x]=idx;
}
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>p[i];
    while(m--){
        int x,y,z;
        cin>>x>>y>>z;
        if(z==1){
            add(h,x,y);add(rh,y,x);
        }
        else{
            add(h,x,y);add(h,y,x);
            add(rh,y,x);add(rh,x,y);
        }
    }
    memset(dmin,0x3f,sizeof dmin);
    q.push(1);
    dmin[1]=p[1];
    while(q.size()){
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值