P1073最优贸易(详解spfa算法与Dijsktra算法的区别)

        这道题是一道隐藏的最短路问题,但是它又蕴含着很多细节值得我们考虑,看了很多人的博客,都只是浅浅地说可以用spfa算法,但是求单源最短路问题还有Dijsktra算法,为什么这道题不能用Dijsktra算法呢,下面是本人的一些理解。

首先看题目:[NOIP2009 提高组] 最优贸易 - 洛谷

       题意很简单,就是阿龙从1号城市出发,中间可以经过任何一个城市,且一个城市可以经过两次,完了可以进行一次买入与一次卖出交易,求最后获取交易的最大值。

       完了我们思考,假设阿龙在 i 城市买入水晶球,在城市 j 卖出水晶球,那么也就是说,阿龙从起点走到城市 i 的路程上买水晶球的最少价格就是price [ i ],从城市 j 走到终点的路程上卖出水晶球的最多价格就是price [ j ] ,也就是说,我们可以把整个路程划分为下面三个部分:

       这样看很直观得把整段路程分为了三部分,那么现在的核心就是如何求出每一段上的每个点对应的买入最小值,卖出最大值。

        我们发现一段路程上,买入最小值永远都是所有已经经过点的最小值,卖出最大值永远都是未来经过点的最大值,于是我们想到了最短路求解,而我们要分别求最小值与最大值,于是我们可以求两边最短路,分别求正向的买入最小值minn[ i ],反向的卖出最大值maxn[ i ],这样枚举每一个点的差值,最大的差值就是最大交易收入

        下面我们就要选择最短路算法,可能很多人想到了Dijsktra算法(我一开始也这样,完了WA了),但是,这道题卡掉了Dijsktra算法,为什么呢?

       因为Dijsktra算法保证每个已经更新了最短距离的点后面距离就不会更新,但是在这道题中,假使你走到城市 i ,后面可能还会有买入价格更低的城市,这样必须还要更新,所以Dijsktra算法行不通,于是我们可以用SPFA算法,每次都将出队的点设置为未遍历,这样后面还会继续更新。

清楚了这两点之后,代码的书写就水到渠成了,下面附上源代码:

#include<iostream>
using namespace std;
#include<cstring>
#include<queue>
 
typedef pair<int,int> PII;
 
const int N = 1e5+10, M = 2e6+10, INF = 0x3f3f3f3f;
int hs[N],e[M],ne[M],w[M],idx;
int price[N],ht[N],n,m,dmax[N],dmin[N];
bool st[N];
 
void add(int h[],int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
 
//flag为真表示求正向走路买入的最小值
void SPFA(int u,int h[],int dist[],bool flag){
    memset(st,0,sizeof(st));
    queue<int>q;
    if(flag) memset(dist,0x3f,sizeof(dmax));
    dist[u]=price[u];
    q.push(u);
    st[u]=true;
    
    while(q.size()){
        int t=q.front();
        q.pop();
        //每次出队后设置为未访问,保证后面距离能继续更新
        st[t]=false;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if((flag&&dist[j]>min(price[j],dist[t]))||(!flag&&dist[j]<max(dist[t],price[j]))) {
                if(flag) dist[j]=min(price[j],dist[t]);
                else dist[j]=max(dist[t],price[j]);
                if(!st[j]){
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
}
 
int main(){
    cin>>n>>m;
    
    for(int i=1;i<=n;i++) scanf("%d",&price[i]);
    
    memset(hs,-1,sizeof(hs));
    memset(ht,-1,sizeof(ht));
    
    int a,b,c;
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
//建立反向图
        add(hs,a,b),add(ht,b,a);
        if(c==2) add(hs,b,a),add(ht,a,b);
    }
    
//这里求最小值从1开始,求最大值从n开始
//因为结合路程图,我们是在1~i城市买入水晶球,在i~n城市卖出水晶球
//所以要从1开始求到达i之前买入水晶球最少价格
//从n开始求到达i之前能够卖出的最大价格,之后做差值
    SPFA(1,hs,dmin,true);
    SPFA(n,ht,dmax,false);
    
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,dmax[i]-dmin[i]);
    
    cout<<res<<endl;
    
    return 0;
}

 这就是这道题的全部内容了,值得思考的点还是很多的,希望各位有所收获!!!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lian潋湄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值