bellman_ford之判断负权回路

城市间货物运输 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;

解题思路

先正常跑 n−1 轮 Bellman-Ford,得到 dist

再跑 第 n 轮

  • 看还能不能被松弛
  • 能松弛的点 = 受到负权回路影响的点

从这些“受影响的点”出发,看能不能到终点 n并且能否从1到这些点

如果能到:

  • 说明可以:
    1 -> ... -> 负环 -> ... -> n
  • 可以无限压低成本 → 输出 “circle”

如果不能到:

  • 这个负环 不会影响 1→n 的最短路结果
  • 就输出正常的最短路 or unconnected
import java.util.*;

public class Main {

    static class Edge {
        int from, to, w;
        Edge(int f, int t, int w) {
            this.from = f;
            this.to = t;
            this.w = w;
        }
    }

    static final int INF = 0x3f3f3f3f;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();

        List<Edge> edges = new ArrayList<>();
        List<Integer>[] graph = new ArrayList[n + 1];
        for (int i = 1; i <= n; i++) graph[i] = new ArrayList<>();

        for (int i = 0; i < m; i++) {
            int s = in.nextInt();
            int t = in.nextInt();
            int v = in.nextInt();
            edges.add(new Edge(s, t, v));
            graph[s].add(t);   // 仅用于后面 BFS 可达性判断
        }

        int[] dist = new int[n + 1];
        Arrays.fill(dist, INF);
        dist[1] = 0;

        // Step 1:标准 Bellman-Ford,跑 n-1 轮
        for (int k = 1; k <= n - 1; k++) {
            boolean updated = false;
            for (Edge e : edges) {
                if (dist[e.from] != INF && dist[e.to] > dist[e.from] + e.w) {
                    dist[e.to] = dist[e.from] + e.w;
                    updated = true;
                }
            }
            if (!updated) break;
        }

        // Step 2:第 n 轮,找还能继续被松弛的点 —— 负环影响点
        boolean[] neg = new boolean[n + 1];
        for (Edge e : edges) {
            if (dist[e.from] != INF && dist[e.to] > dist[e.from] + e.w) {
                neg[e.to] = true;
            }
        }

        // Step 3:从所有 neg 点出发,检查是否能到达 n
        boolean[] vis = new boolean[n + 1];
        Queue<Integer> q = new ArrayDeque<>();

        for (int i = 1; i <= n; i++) {
            if (neg[i]) {
                q.offer(i);
                vis[i] = true;
            }
        }

        boolean affect = false;
        while (!q.isEmpty()) {
            int u = q.poll();
            if (u == n) {
                affect = true;
                break;
            }
            for (int v : graph[u]) {
                if (!vis[v]) {
                    vis[v] = true;
                    q.offer(v);
                }
            }
        }

        if (affect) {
            System.out.println("circle");
        } else if (dist[n] == INF) {
            System.out.println("unconnected");
        } else {
            System.out.println(dist[n]);
        }
    }
}

bellman_ford之单源有限最短路

96.城市间货物运输 III

题目描述

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

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

请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。

输入描述

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

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

最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。

输出描述

输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 “unreachable”,表示不存在符合条件的运输方案。

输入示例

6 7
1 2 1
2 4 -3
2 5 2
1 3 5
3 5 1
4 6 4
5 6 -2
2 6 1

输出示例

0

提示信息

从 2 -> 5 -> 6 中转一站,运输成本为 0。

1 <= n <= 1000;

1 <= m <= 10000;

-100 <= v <= 100;

解题思路

import java.util.*;

public class Main {

    static class Edge {
        int from, to, value;
        Edge(int f, int t, int value) {
            this.from = f;
            this.to = t;
            this.value = value;
        }
    }

    static final int INF = 0x3f3f3f3f;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int n = in.nextInt();
        int m = in.nextInt();

        List<Edge> edges = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int s = in.nextInt();
            int t = in.nextInt();
            int v = in.nextInt();
            edges.add(new Edge(s, t, v));
        }

        int src = in.nextInt();
        int dst = in.nextInt();
        int k   = in.nextInt();

        // prev[v]:在当前限制(使用的边数)下,从 src 到 v 的最小成本
        int[] prev = new int[n + 1];
        Arrays.fill(prev, INF);
        prev[src] = 0; // 起点,0 条边时只能在 src

        // 每一轮相当于允许多使用一条边
        for (int step = 1; step <= k + 1; step++) {

            // cur 是这一轮更新后的结果,先复制上一轮
            int[] cur = prev.clone();  // 关键:时间层隔离

            // 尝试用“上一层的路径 + 一条边”来更新本层的距离
            for (Edge e : edges) {
                if (prev[e.from] != INF) {
                    int newDist = prev[e.from] + e.value;
                    if (newDist < cur[e.to]) {
                        cur[e.to] = newDist;
                    }
                }
            }

            // 推进到下一层
            prev = cur;
        }

        if (prev[dst] == INF) {
            // 在给定的中转次数限制下,到不了 dst
            System.out.println("unreachable");
        } else {
            // 找到了最小运输成本(可能是负数 = 盈利)
            System.out.println(prev[dst]);
        }
    }
}