Day60-图论,卡码网94,95,96

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


  • 思路
  • Bellman_ford 算法 每次都是对所有边进行松弛,其实是多做了一些无用功。
  • 只需要对 上一次松弛的时候更新过的节点作为出发节点所连接的边 进行松弛就够了。
  • 使用minDist数组来表达 起点到各个节点的最短距离,例如minDist[3] = 5 表示起点到达节点3 的最小距离为5
  • SPFA(队列优化版Bellman_ford),用于求解单源最短路径问题,是Bellman-Ford算法的一种优化版本,通过队列优化减少了不必要的松弛操作
/**
 * 1. 输入处理部分
    读取节点数n和边数m
    使用邻接表grid存储图的边信息,格式为{源节点: [[目标节点1, 权重1], [目标节点2, 权重2], ...]}
 * 2. 初始化
 * 3. SPFA算法核心
 * 4. 结果输出
 
 * 执行流程示例
输入:
4 5
1 2 2
1 3 4
2 3 1
2 4 5
3 4 1

执行过程:
1.初始:队列=[1], minDist=[∞,0,∞,∞,∞]
2.处理节点1:
    更新节点2距离为2,加入队列
    更新节点3距离为4,加入队列
3.处理节点2:
    更新节点3距离为3
    更新节点4距离为7,加入队列
4.处理节点3:
    更新节点4距离为4
5.处理节点4:
    无更新
6.输出结果:4(路径1→2→3→4)
 */
async function main() {
    // 輸入
    const rl = require('readline').createInterface({ input: process.stdin })
    const iter = rl[Symbol.asyncIterator]()
    const readline = async () => (await iter.next()).value
    const [n, m] = (await readline()).split(" ").map(Number)
    const grid = {}
    for (let i = 0 ; i < m ; i++) {
        const [src, desc, w] = (await readline()).split(" ").map(Number)
        if (grid.hasOwnProperty(src)) {
            grid[src].push([desc, w])
        } else {
            grid[src] = [[desc, w]]
        }
    }

    // 初始化
    const minDist = Array.from({length: n + 1}, () => Number.MAX_VALUE)
    // 起点设为节点1,距离为0
    minDist[1] = 0
    // 队列初始化,包含起点
    const q = [1]
    // 标记节点是否在队列中
    const visited = Array.from({length: n + 1}, () => false)
    
    // SPFA算法核心
    while (q.length) {
        // 取出队列头部节点
        const src = q.shift()
        // 获取该节点的所有邻接边
        const neighbors = grid[src]
        // 从队列里取出的时候,要取消标记,我们只保证已经在队列里的元素不用重复加入
        visited[src] = false  // 标记该节点已出队
        if (neighbors) {
            for (const [desc, w] of neighbors) {
                // 开始松弛
                if (minDist[src] !== Number.MAX_VALUE 
                && minDist[src] + w < minDist[desc]) {
                    minDist[desc] = minDist[src] + w
                    // 已经在队列里的元素不用重复添加
                    if (!visited[desc]) {
                        q.push(desc)
                        visited[desc] = true
                    }
                    
                }            
            }            
        }
    }
    
    // 輸出
    if (minDist[n] === Number.MAX_VALUE) {
        // 不能到达终点
        console.log('unconnected')
    } else {
        // 到达终点最短路径
        console.log(minDist[n])
    }
}

main()


  1. 城市间货物运输 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. 输入处理
    使用Node.js的readline模块读取输入数据
    第一行读取节点数n和边数m
    后续读取每条边的信息并构建邻接表
 * 2. 邻接表结构
    const grid = new Array(n + 1).fill().map(() => []);
    grid是一个数组,每个元素是一个数组,存储该节点的出边
    每个边是一个对象,包含to(目标节点)和val(权重)
 * 3. SPFA算法初始化
    minDist: 存储起点到各节点的最短距离,初始为Number.MAX_SAFE_INTEGER
    queue: SPFA算法的工作队列,初始包含起点
    count: 记录每个节点入队次数,用于检测负权环
    inQueue: 标记节点是否在队列中,避免重复入队
 * 4. SPFA主算法
    1.从队列取出节点
    2.遍历该节点的所有邻接边
    3.对每条边尝试松弛操作
    4.如果发现更短路径:
        更新最短距离
        如果目标节点不在队列中,则加入队列
        检查入队次数是否超过n次(负权环检测)
 * 5. 结果输出
    如果检测到负权环,输出"circle"
    如果终点不可达,输出"unconnected"
    否则输出起点到终点的最短距离
 */
async function main() {
    const readline = require('readline');
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    const lines = [];
    for await (const line of rl) {
        lines.push(line);
    }

    // 解析输入
    const [n, m] = lines[0].split(' ').map(Number);
    
    // 构建邻接表
    const grid = new Array(n + 1).fill().map(() => []);
    
    for (let i = 1; i <= m; i++) {
        const [p1, p2, val] = lines[i].split(' ').map(Number);
        grid[p1].push({ to: p2, val });
    }

    const start = 1; // 起点
    const end = n;   // 终点

    // 初始化最短距离数组
    const minDist = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER);
    minDist[start] = 0;

    // SPFA队列初始化
    const queue = [start];
    const count = new Array(n + 1).fill(0); // 记录节点入队次数
    count[start]++;
    const inQueue = new Array(n + 1).fill(false); // 记录节点是否在队列中
    inQueue[start] = true;

    let hasNegativeCycle = false; // 标记是否存在负权环

    // SPFA算法主循环
    while (queue.length > 0 && !hasNegativeCycle) {
        const node = queue.shift();
        inQueue[node] = false; // 节点出队

        // 遍历当前节点的所有邻接边
        for (const edge of grid[node]) {
            const from = node;
            const to = edge.to;
            const value = edge.val;

            // 松弛操作
            if (minDist[to] > minDist[from] + value) {
                minDist[to] = minDist[from] + value;

                // 如果目标节点不在队列中,则加入队列
                if (!inQueue[to]) {
                    queue.push(to);
                    inQueue[to] = true;
                    count[to]++;

                    // 检测负权环:如果节点入队次数超过n次
                    if (count[to] > n) {
                        hasNegativeCycle = true;
                        queue.length = 0; // 清空队列
                        break;
                    }
                }
            }
        }
    }

    // 输出结果
    if (hasNegativeCycle) {
        console.log("circle"); // 存在负权环
    } else if (minDist[end] === Number.MAX_SAFE_INTEGER) {
        console.log("unconnected"); // 终点不可达
    } else {
        console.log(minDist[end]); // 输出最短路径长度
    }
}

main();


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


  • 思路
/**
 * 1. 输入处理
    读取节点数n和边数m
    读取所有边的信息(起点、终点、权重)
    读取查询参数:起点src、终点dst和最大允许的边数k
 * 2. 算法核心思想
    通过k+1次松弛操作,计算最多经过k条边的最短路径
    每次松弛操作基于上一次的结果,确保不会在一次迭代中使用多条边
 * 3. 关键数据结构
    minDist数组:记录当前到各节点的最短距离
    minDistCopy数组:保存上一次迭代的结果,防止"过度松弛"
 * 4. 算法流程
    初始化:设置起点距离为0,其他节点距离为无穷大
    迭代松弛:
        进行k+1次循环(因为最多允许k条边)
        每次循环复制当前距离数组(minDistCopy)
        基于minDistCopy进行松弛操作,确保每次迭代只增加一条边
    结果检查:判断终点是否可达并输出结果

 * 示例分析
输入
4 5  // 4个节点,5条边
1 2 2  // 边1→2,权重2
1 3 4  // 边1→3,权重4
2 3 1  // 边2→3,权重1
2 4 5  // 边2→4,权重5
3 4 1  // 边3→4,权重1
1 4 2  // 查询:从1到4,最多2条边

执行过程:
1.初始:minDist = [∞, 0, ∞, ∞, ∞]
2.第一次迭代(允许1条边):
    更新节点2距离为2
    更新节点3距离为4
3.第二次迭代(允许2条边):
    通过1→2→3更新节点3距离为3
    通过1→2→4更新节点4距离为7
    通过1→3→4更新节点4距离为5
4.结果:5(路径1→3→4)
 */
async function main() {
    const readline = require('readline');
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    const lines = [];
    for await (const line of rl) {
        lines.push(line);
    }

    // 解析输入数据
    let index = 0;
    const [n, m] = lines[index++].split(' ').map(Number);
    
    // 读取所有边
    const edges = [];
    for (let i = 0; i < m; i++) {
        const [p1, p2, val] = lines[index++].split(' ').map(Number);
        edges.push([p1, p2, val]);
    }

    const [src, dst, k] = lines[index++].split(' ').map(Number);

    // 初始化距离数组
    let minDist = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER);
    minDist[src] = 0; // 起点到自身的距离为0

    // 执行k+1次松弛操作
    for (let i = 1; i <= k + 1; i++) {
        // 保存上一次迭代的结果
        const minDistCopy = [...minDist];
        
        // 遍历所有边进行松弛操作
        for (const [from, to, price] of edges) {
            if (minDistCopy[from] !== Number.MAX_SAFE_INTEGER && 
                minDist[to] > minDistCopy[from] + price) {
                minDist[to] = minDistCopy[from] + price;
            }
        }
    }

    // 输出结果
    if (minDist[dst] === Number.MAX_SAFE_INTEGER) {
        console.log("unreachable");
    } else {
        console.log(minDist[dst]);
    }
}

main();



参考&感谢各路大神

posted @ 2025-07-26 20:20  安静的嘶吼  阅读(12)  评论(0)    收藏  举报