341 最优贸易(单源最短路径 + dp)

1. 问题描述:

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。商人阿龙来到 C 国旅游。当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。注意:本题数据有加强。

输入格式

第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。

输出格式

一个整数,表示答案。

数据范围

1 ≤ n ≤ 100000,
1 ≤ m ≤ 500000,
1 ≤ 各城市水晶球价格 ≤ 100

输入样例:

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

输出样例:

5
来源:https://www.acwing.com/problem/content/343/

2. 思路分析:

这道题目其实是最短路径与dp的结合版,其实最短路径与dp问题的交集很大,绝大部分的dp问题都可以转为最短路径问题,如果属于无环的情况那么属于拓扑图上的最短路径问题,当dp问题中有依赖关系的时候其实可以使用最短路径的方式来求解最优解,也即存在环的时候可以使用最短路径来解决状态之间的依赖关系;对于这道题目来说其实问的是从1~n的所有路径中先买后卖,也即只交易一次能够获得的最大收益,因为题目的数据可能存在环,所以我们使用最短路径来解决而不是使用dp来解决,但是可以使用dp问题的思路来分析问题,我们其实是可以将1~n的所有路径分为n份(相当于是集合的划分),其中第k份表示的是第k个点之前买入和第k个点之后卖出的分界点(包含第k个点买入和卖出的情况),我们可以求解出以这个分界点k的左右两边的最大值与最小值的差值那么就是以当前的k为分界点的最大收益,枚举所有的分界点取一个max就是所有分界点的最大收益,我们可以使用dmin和dmax分别存储某个点的买入和卖出的最小值和最大值,并且这里在建图的时候使用了一个技巧,ht存储顺着建的图,ht存储反着建的图,这样当我们求解dmin的时候可以使用顺着建的图hs来求解,求解dmax的时候使用反着建的图ht来求解,做两遍spfa即可求解出dmin和dmax,最后枚举所有的分界点求解最大差值即可。注意这里不能够使用dijkstra算法来求解,这要从算法的原理上理解,dijkstra算法中对于选择的每一个更新的点都是只会被更新一次的,而对于这道题目来说有可能后面的点会更新到之前的点所以之前的点有可能被多次更新,这样就不能够使用dijkstra算法了,只能够使用bellman-ford或者是spfa算法,spfa算法是以边数进行考虑的,每一次考虑边数不超过k的前提下所有的最短路径,所以到终点的时候一定可以求解出最值。

3. 代码如下:

from typing import List
import collections


class Solution:
    # 根据type的类型确定求解的是最小值还是最大值
    def spfa(self, n: int, type: int, w: List[int], dis: List[int], g: List[List[int]]):
        q = collections.deque()
        vis = [0] * (n + 1)
        if type == 1:
            dis[1] = w[1]
            q.append(1)
        else:
            dis[n] = w[n]
            q.append(n)
        while q:
            p = q.popleft()
            vis[p] = 0
            for next in g[p]:
                # 根据属于哪一种情况求解对应的最值
                if (type == 1 and dis[next] > min(dis[p], w[next])) or (type == 2 and dis[next] < max(dis[p], w[next])):
                    if type == 1:
                        dis[next] = min(dis[p], w[next])
                    else:
                        dis[next] = max(dis[p], w[next])
                    if vis[next] == 0:
                        q.append(next)
                        vis[next] = 1

    def process(self):
        n, m = map(int, input().split())
        # hs表示顺着建的图
        hs = [list() for i in range(n + 1)]
        # 求解卖出的最大价格的时候使用反向建图的方式, ht存储反向建的图, 如a->b则存储b->a
        ht = [list() for i in range(n + 1)]
        # 为了使得下标从1开始访问所以在最前面加上一个0
        w = [0] + list(map(int, input().split()))
        for i in range(m):
            a, b, c = map(int, input().split())
            hs[a].append(b)
            ht[b].append(a)
            if c == 2:
                hs[b].append(a)
                ht[a].append(b)
        INF = 10 ** 10
        dmin, dmax = [INF] * (n + 1), [-INF] * (n + 1)
        self.spfa(n, 1, w, dmin, hs)
        self.spfa(n, 2, w, dmax, ht)
        # 枚举以每一个点为分界点的最小值与最大值的差值
        res = 0
        for i in range(1, n + 1):
            res = max(res, dmax[i] - dmin[i])
        return res


if __name__ == "__main__":
    print(Solution().process())
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值