Day60-图论,卡码网94,95,96
- 城市间货物运输 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()
- 城市间货物运输 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();
- 城市间货物运输 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();

浙公网安备 33010602011771号