DP与最短路联系 && 最优贸易 —— DP + 最短路

DP与最短路联系

0 − 1 0-1 01背包为例

转移方程为

f(i,j)=max(f(i-1,j),f(i-1,j-w)+v)

可以看成图论中的路径;

在这里插入图片描述

有以下关系;
在这里插入图片描述
这启发我们,如果一个DP问题的依赖关系不具有拓扑序,那么我们可以用最短路算法来解决这种DP不能解决的问题;


比如说像下图这种形成了环路的,用 D P DP DP是没法解决的;
在这里插入图片描述
但是我们可以用最短路来解决;

题面

传送门
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

要求一次买入卖出可以得到的最大收益;

我们设定集合为先买后卖的所有方案中的最大值;

将上述集合划分成若干个子集;

在这里插入图片描述
分别是以 1 1 1号点, 2 2 2号点,…, n n n号点为买卖分界点的方案;

买卖分界点指的是:在某个点之前买入,在某个点之后卖出,注意买卖都是包含这个点的;

这样我们就可以分成若干个不遗漏的子集(有重复,但是求最值不影响);


只要我们求出每一类的最大值,取一个 m a x max max即可;

对于第 k k k类来说,我们需要求从 [ 1 , k ] [1,k] [1,k]买入的最小值以及求 [ k , n ] [k,n] [k,n]卖出的最大值;

不难发现 [ 1 , k ] [1,k] [1,k] [ k , n ] [k,n] [k,n]是独立的;

因此第 k k k类最大值就是 [ k , n ] m a x − [ 1 , k ] m i n [k,n]_{max}-[1,k]_{min} [k,n]max[1,k]min


由于这种依赖关系不是拓扑序,是可能有环的,因此这里我们不能用 D P DP DP来解决,而需要用最短路来解决;


因此我们只需要正向和反向各跑一次最短路即可;

接着考虑求最短路的算法;


因为是非负权的,我们考虑SPFA迪杰斯特拉;

迪杰斯特拉能不能用的一个关键点在于,从堆中取出一个最小值,断定这个点不再会被其他点更新,那么我们就可以使用;

也就是要使用迪杰斯特拉的前提条件是明确知道起点一定是最短的,因为基于贪心的策略,每次都是使用距离最短的点去更新其他的点,如果不能保证起点是最短的就不能用


而这道题,由于环的存在再加上这个权值的定义是路径某一点的值;

因此有可能发生出去跑几圈到达最便宜的点又回到起点,这时起点就会又变小,这样我们就不能确保起点最小,那么就寄了;


接着考虑SPFA算法,而SPFA本质就是Bellman-ford算法;

Bellman-ford一般来说都是正确的;

因为它是基于边数来考虑的,只要最短路径经过的边数是小于等于 n − 1 n-1 n1的,就一定是正确的;

因为点数为 n n n,边数为 n − 1 n-1 n1时,就已经包含所有点了;

这是Bellman-ford的算法流程;

在这里插入图片描述
因此这题可以用SPFA来写;

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 100000 + 10,M = 500000 + 10;

struct Edge{
    int next,to,val;
}e[M<<2];//双向边 + 反向图

int tot;

//start_head,tail_head 正向边和反向边
int sh[N],th[N],a[N];

int distmx[N],distmn[N];
bool vis[N];
int n,m;

void add(int head[],int u,int v){
    e[++tot].to = v;
    e[tot].next = head[u];
    head[u] = tot;
}
void into_que(queue<int>&que,int to){
    if(vis[to] == 0){
        que.push(to);
        vis[to] = 1;
    }
}
void spfa(int head[],int dist[],int type){
    queue<int> que;
    memset(vis,0,sizeof vis);
    //正向跑
    if(type == 1){
        memset(dist,0x3f,sizeof distmx);
        dist[1] = a[1];
        que.push(1);
        vis[1] = 1;
    }
    else{
        memset(dist,-0x3f,sizeof distmn);
        dist[n] = a[n];
        que.push(n);
        vis[n] = 1;
    }
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = false;
        for(int i=head[u];i;i=e[i].next){
            int to = e[i].to;
            if(type == 1){ //求最小值
                //取自己 或者取来源点
                if(dist[to] > min(dist[u],a[to])){
                    dist[to] = min(dist[u],a[to]);
                    into_que(que,to);
                }
            }
            else{ //求最大值
                if(dist[to] < max(dist[u],a[to])){
                    dist[to] = max(dist[u],a[to]);
                    into_que(que,to);
                }
            }
        }
    }
}
void solve(){
    cin >> n >> m;
    for(int i=1;i<=n;++i) cin >> a[i];
    while(m--){
        int u,v,type;
        cin >> u >> v >> type;
        add(sh, u, v), add(th, v, u);
        if(type == 2) add(sh, v, u), add(th, u, v);
    }
    spfa(sh,distmn,1);
    spfa(th,distmx,2);
    //枚举每一类 取一个max
    int ans = 0;
    for(int i=1;i<=n;++i){
        ans = max(ans,distmx[i] - distmn[i]);
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值