代码随想录算法训练营第六十天| 94. 城市间货物运输 I (SPFA )、95. 城市间货物运输 II

[KamaCoder] 94. 城市间货物运输 I

[KamaCoder] 94. 城市间货物运输 I 文章解释

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。

城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。

输出描述

如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 "unconnected"。

输入示例
6 7
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5
输出示例
1

示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。

示例 2:

4 2
1 2 -1
3 4 -1

在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 "unconnected"。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

[KamaCoder] 94. 城市间货物运输 I (SPFA )

自己看到题目的第一想法

    无

看完代码随想录之后的想法

    之前的 Dijkstra 算法, 需要路径权值非负数, 因为一旦访问过的节点, 就不会再访问了. 对于 Dijkstra 算法, 如果一个节点距离原点路径权值的和为 N, 且当前的 N 是所有可触达的节点中最小的, 那么从其他节点再触达当前节点, 路径权值的和一定是小于等于 N 的. 因此在 Dijkstra 中, 一个节点不能多次访问.

    然而当出现负权值的时候, 一个节点是需要多次访问的. 而 Bellman_Ford 算法就是不停的计算节点的值, 一共计算 N - 1 次, N 为节点的数量. 因为在一个有 N 个节点的图中, 一个节点最多和 N - 1 个节点关联, 因此通过 N - 1 次松弛, 就可以计算出所有节点到原始节点的最小距离.

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
    
    private static Map<Integer, List<Edge>> nodes = new HashMap<>();
    private static int[] minDist = null;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int nodeCount = 0;
        while (scanner.hasNext()) {
            nodeCount = scanner.nextInt();
            int sideCount = scanner.nextInt();
            
            minDist = new int[nodeCount + 1];
            Arrays.fill(minDist, Integer.MAX_VALUE);
            
            int from;
            int to;
            int value;
            for (int i = 0; i < sideCount; i++) {
                Edge edge = new Edge();
                edge.from = scanner.nextInt();
                edge.to = scanner.nextInt();
                edge.value = scanner.nextInt();
                List<Edge> edges = nodes.get(edge.from);
                if (edges == null) {
                    edges = new ArrayList<>();
                    nodes.put(edge.from, edges);
                }
                edges.add(edge);
            }
        }
        
        minDist[1] = 0;
        
        Deque<Integer> dealedNodes = new LinkedList<>();
        dealedNodes.add(1);
        while (!dealedNodes.isEmpty()) {
            int node = dealedNodes.pop();
            List<Edge> toEdges = nodes.get(node);
            if (toEdges == null) {
                continue;
            }
            for (Edge edge : toEdges) {
                if (minDist[node] + edge.value < minDist[edge.to]) {
                    minDist[edge.to] = minDist[node] + edge.value;
                    if (!dealedNodes.contains(edge.to)) {
                        dealedNodes.addLast(edge.to);
                    }
                }
            }
        }
        if (minDist[nodeCount] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(minDist[nodeCount]);
        }
    }
    
    private static class Edge {
        int from;
        int to;
        int value;
    }
}

自己实现过程中遇到哪些困难

    无

[KamaCoder] 95. 城市间货物运输 II

[KamaCoder] 95. 城市间货物运输 II 文章解释

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。

城市 1 到城市 n 之间可能会出现没有路径的情况

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。

输出描述

如果没有发现负权回路,则输出一个整数,表示从城市 1 到城市 n 的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 "circle"。如果从城市 1 无法到达城市 n,则输出 "unconnected"。

输入示例
4 4
1 2 -1
2 3 1
3 1 -1 
3 4 1
输出示例
circle
提示信息

路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 "circle" 表示已经检测出了该种情况。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

[KamaCoder] 95. 城市间货物运输 II

自己看到题目的第一想法

    无

看完代码随想录之后的想法

    在一个无负权回路的图中, 如果一共有 N 个节点, 那么经过 N - 1 次松弛, 则 minDist 数组一定就稳定了. 而在有负权回路的图中, 在第 N 次松弛的时候, 就会出现 minDist 数组需要更新的情况. 因此只要松弛 N 次, 如果第 N 次出现 minDist 需要被更新的情况, 就是出现了负权回路.

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
    
    private static Map<Integer, List<Edge>> nodes = new HashMap<>();
    private static int[] minDist = null;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int nodeCount = 0;
        while (scanner.hasNext()) {
            nodeCount = scanner.nextInt();
            int sideCount = scanner.nextInt();
            
            minDist = new int[nodeCount + 1];
            Arrays.fill(minDist, Integer.MAX_VALUE);
            
            for (int i = 0; i < sideCount; i++) {
                Edge edge = new Edge();
                edge.from = scanner.nextInt();
                edge.to = scanner.nextInt();
                edge.value = scanner.nextInt();
                List<Edge> edges = nodes.get(edge.from);
                if (edges == null) {
                    edges = new ArrayList();
                    nodes.put(edge.from, edges);
                }
                edges.add(edge);
            }
        }
        minDist[1] = 0;
        boolean flag = false;
        for (int i = 1; i <= nodeCount; i++) {
            for (Map.Entry<Integer, List<Edge>> entry : nodes.entrySet()) {
                List<Edge> edges = entry.getValue();
                if (edges == null) {
                    continue;
                }
                for (Edge edge : edges) {
                    if (minDist[edge.from] == Integer.MAX_VALUE 
                        || minDist[edge.from] + edge.value >= minDist[edge.to]) {
                            continue;
                    }
                    if (i < nodeCount) {
                        minDist[edge.to] = minDist[edge.from] + edge.value;
                    } else {
                        flag = true;
                        break;
                    }
                }
            }
        }
        if (flag) {
            System.out.println("circle");
        } else if (minDist[nodeCount] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(minDist[nodeCount]);
        }
    }
    
    private static class Edge {
        int from;
        int to;
        int value;
    }
}

自己实现过程中遇到哪些困难

    重点是思路, 实现上就是在松弛所有边的时候判断当前是第几次松弛, 如果是第 N 次(N 为节点个数), 且出现 minDist 需要更新, 那就是出现了负权回路.

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是SPFA算法记录路径的C++代码模板: ```c++ #include <iostream> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005; const int INF = 0x3f3f3f3f; struct Edge { int to, w; }; vector<Edge> edges[MAXN]; // 存储图的邻接表 int dist[MAXN]; // 存储源点到各个点的最短距离 int pre[MAXN]; // 存储路径上每个点的前驱节点 bool inQueue[MAXN]; // 标记每个点是否在队列中 void SPFA(int s, int n) { queue<int> q; memset(dist, INF, sizeof(dist)); memset(inQueue, false, sizeof(inQueue)); memset(pre, -1, sizeof(pre)); dist[s] = 0; pre[s] = s; inQueue[s] = true; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); inQueue[u] = false; for (int i = 0; i < edges[u].size(); i++) { int v = edges[u][i].to; int w = edges[u][i].w; if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w; pre[v] = u; // 更新前驱节点 if (!inQueue[v]) { inQueue[v] = true; q.push(v); } } } } } void printPath(int s, int t) { vector<int> path; for (int i = t; i != s; i = pre[i]) { // 从终点往回找前驱节点 path.push_back(i); } path.push_back(s); reverse(path.begin(), path.end()); // 反转路径,使其从起点到终点 for (int i = 0; i < path.size(); i++) { cout << path[i] << " "; } cout << endl; } int main() { int n, m, s, t; cin >> n >> m >> s >> t; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; edges[u].push_back({v, w}); edges[v].push_back({u, w}); // 无向图 } SPFA(s, n); if (dist[t] == INF) { cout << "No path!" << endl; } else { cout << "Shortest path: " << dist[t] << endl; cout << "Path: "; printPath(s, t); } return 0; } ``` 在SPFA算法中,每个节点的前驱节点存储在pre数组中。当算法执行完毕后,可以使用pre数组从终点往回找前驱节点,直到找到起点为止,即可得到最短路径。这里使用一个vector来存储路径上的节点,最后反转vector中的元素,使其从起点到终点输出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值