787. K 站中转内最便宜的航班

787. K 站中转内最便宜的航班

难度中等317

有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 toi 抵达 pricei

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1

示例 1:

输入: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
输出: 200
解释: 
城市航班图如下

从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。

示例 2:

输入: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
输出: 500
解释: 
城市航班图如下

从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。

提示:

  • 1 <= n <= 100
  • 0 <= flights.length <= (n * (n - 1) / 2)
  • flights[i].length == 3
  • 0 <= fromi, toi < n
  • fromi != toi
  • 1 <= pricei <= 104
  • 航班没有重复,且不存在自环
  • 0 <= src, dst, k < n
  • src != dst

思路:首先需要转换一下题意,经过k个中转到达dst,可以等价为走过了k+1步到达dst。定义dp[src_i][k_t]表示当前处于src,且已经走了k_t步的最小代价,那么假如有src_i->src_j,就可以知道dp[src_j][k_t+1] = min(dp[src_j][k_t+1], dp[src_i][k_t] + price_i_j])。

[注]:本题有个大坑,因为我想用的是每次保存src_i,k_t,用于更新src_j,k_t+1,也就是正向生成,而不是反推当前状态由哪些状态可以转移得到,因此在转移后需要将状态入队列供后续使用,但是!!请注意,对于入节点的状态需要进行标记,否则可能会入多次。

例如,src_i->src_j且src_z->src_j,那么dp[src_i][k_t]和dp[src_z][k_t]就会同时对dp[src_j][k_t+1]进行更新,也就是说src_j、k_t+1会入队列多次,会超时,因此可以用一个标记数组防止重复入队列。

class Solution {
public:

    int Next[5000], To[5000], Head[110], W[5000], cnt, dp[110][110];
    bool mark[110][110];

    struct Point{
        int src, tempk;
    };

    void add(int from, int to, int price) {
        W[cnt] = price;
        To[cnt] = to;
        Next[cnt] = Head[from];
        Head[from] = cnt ++; 
    }

    int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
        memset(Next, -1, sizeof(Next));
        memset(To, -1, sizeof(To));
        memset(Head, -1, sizeof(Head));
        memset(W, -1, sizeof(W));
        memset(mark, 0, sizeof(mark));
        cnt = 0;
        int m = flights.size(), minValue = 0x3f3f3f3f, to, price, tempk, i;
        for(i = 0; i < m; ++ i) {
            add(flights[i][0], flights[i][1], flights[i][2]);
        }
        memset(dp, 0x3f, sizeof(dp));
        dp[src][0] = 0;

        queue<Point> que;
        que.push(Point{src, 0});

        Point point;

        while(!que.empty()) {
            point = que.front();
            que.pop();

            src = point.src; tempk = point.tempk;
            if(src == dst) {
                minValue = min(minValue, dp[src][tempk]);
            }

            if(tempk >= k + 1) { //最多k个中转 等价于 最多 第k + 1班到达目的地
                continue;
            }   

            for(i = Head[src]; i != -1; i = Next[i]) {
                to = To[i];
                price = W[i];
                dp[to][tempk + 1] = min(dp[to][tempk + 1], dp[src][tempk] + price);
                if(tempk >= k && to != dst) continue;
                if(!mark[to][tempk + 1]) {
                    mark[to][tempk + 1] = true;
                    que.push(Point{to, tempk + 1});
                }
            }
        }
        return minValue == 0x3f3f3f3f ? -1 : minValue;
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值