目录
题目
思路
Code
物流公司每天都要处理很多物流的运输工作,整个城市共有N 个地点。共有 N-1条公路,每2个地点之间都能通过公路连通。物流公司总部位于1号地点。
今天有一辆物流运偷车共有M条物流运输任务,物流运输车每天的工作流程如下:
先要从总部出发去收取所有的寄件货物,收到所有货物后回到总部扫描货物,再从总部出发将货物送至所有的送件地址,送完后最终回到总部,算作完成了今天的运输工作,
请问该辆物流运输车今天最少行驶多少路程可以完成今天的运输工作,运输任务不分先后。
输入描述
对于每组数据,第一行有2个整数,依次为 N(3接下来有N - 1行,每行有3个整数,依次为
u(1 < u≤ N),v(1 < v< N),c(1接下来有M 行,每行有2个整数,依次为 s(2 ≤s ≤ N), t(2 ≤t≤ N,s≠t), 表示寄件任务从s寄到t 输出描述
输出一个整数,表示该辆物流运输车最少行驶多少路程能够完成今天的运输工作。
示例1:
输入:
4 2
2 1 1
1 3 2
4 3 2
3 2
4 2输出:
10
说明:
运输车从地点1开到地点3接收任务1物品,再开到地点4接收任务2物品,回到总部1扫描,扫描后将任务1和任务2的物品
送到地点2,最终回到总部1,总共行驶里程10。
示例2:
输入:
5 2
2 1 1
1 3 2
4 3 2
1 5 3
4 2
5 4输出:
24
说明:
运输车从地点1开到地点5接收任务2物品,再开到地点4接收任务1物品,回到总部1扫描,扫描后将开到地点4完成任务2再开到地点2完成任务1,最终回到总部1,总共行驶里程24
1. 问题分析
1.1 题目理解
输出要求的具体说明:
- 计算物流车完成所有任务的最少行驶路程
- 必须严格按照:收件阶段→回总部→送件阶段→回总部的流程
约束条件的整理:
- 城市为树结构(N个点,N-1条边,连通)
- 必须从总部1出发和返回
- 收件和送件可以任意安排访问顺序
- 每个任务包含收件地点和送件地点
1.2 问题建模
数据结构转化:
- 将城市建模为带权重的树
- 收件阶段:从1出发访问所有收件地点后回到1
- 送件阶段:从1出发访问所有送件地点后回到1
算法类型确定:
- 树上的最小生成树遍历问题
- 需要找到包含指定节点集合的最小连通子树(Steiner树)
数学模型:
- 目标:min(收件阶段距离 + 送件阶段距离)
- 每个阶段的最优距离 = 2 × 包含目标节点的最小连通子树的边权和
2. 解题思路
2.1 算法选择
最优算法:树上Steiner树遍历算法
- 对于给定的目标节点集合,找到包含这些节点和起点的最小连通子树
- 遍历该子树的最短距离为子树所有边权重的2倍
复杂度分析:
- 时间复杂度:O(N + M),需要DFS构建树和处理任务
- 空间复杂度:O(N),存储树结构和访问标记
2.2 核心数据结构
主要数据结构选择:
- 邻接表表示树结构
- 集合存储收件和送件地点
- DFS遍历时使用访问标记数组
实现方式:
- 使用邻接表存储树的边和权重
- 用集合去重收集收件和送件地点
- 通过DFS标记路径计算Steiner树权重
2.3 关键算法步骤
- 构建树结构:用邻接表存储所有边和权重
- 提取目标节点:分别收集所有收件地点和送件地点
- 计算Steiner树权重:
- 对于目标节点集合,从节点1开始DFS
- 标记从1到每个目标节点路径上的所有边
- 计算被标记边的权重总和
- 计算总距离:2 × (收件Steiner树权重 + 送件Steiner树权重)
from collections import defaultdict, deque
def solve():
"""
物流运输最短路径问题求解
Returns:
int: 最少行驶路程
"""
def dfs_mark_path(graph, start, targets, visited_edges):
"""
DFS标记从起点到所有目标点路径上的边
Args:
graph: 邻接表表示的树
start: 起始节点
targets: 目标节点集合
visited_edges: 用于记录访问过的边的集合
Returns:
bool: 当前子树是否包含目标节点
"""
# 检查当前节点是否为目标节点
has_target = start in targets
# 遍历所有邻接节点
for neighbor, weight in graph[start]:
# 避免重复访问边
edge = tuple(sorted([start, neighbor]))
if edge not in visited_edges:
visited_edges.add(edge)
# 递归DFS邻接节点
if dfs_mark_path(graph, neighbor, targets, visited_edges):
has_target = True
# 记录这条边及其权重
edge_weights[edge] = weight
else:
# 如果这条路径不通向目标节点,移除标记
visited_edges.remove(edge)
return has_target
def calculate_steiner_tree_weight(graph, targets):
"""
计算包含目标节点集合的Steiner树权重
Args:
graph: 邻接表表示的树
targets: 目标节点集合
Returns:
int: Steiner树的总权重
"""
if not targets:
return 0
visited_edges = set()
global edge_weights
edge_weights = {}
# 从节点1开始DFS标记路径
dfs_mark_path(graph, 1, targets, visited_edges)
# 计算所有标记边的权重总和
return sum(edge_weights.values())
# 读取输入
n, m = map(int, input().split())
# 构建树的邻接表
graph = defaultdict(list)
for _ in range(n - 1):
u, v, c = map(int, input().split())
graph[u].append((v, c))
graph[v].append((u, c))
# 收集收件和送件地点
pickup_locations = set() # 收件地点集合
delivery_locations = set() # 送件地点集合
for _ in range(m):
s, t = map(int, input().split())
pickup_locations.add(s) # s是收件地点
delivery_locations.add(t) # t是送件地点
# 计算收件阶段的Steiner树权重
pickup_weight = calculate_steiner_tree_weight(graph, pickup_locations)
# 计算送件阶段的Steiner树权重
delivery_weight = calculate_steiner_tree_weight(graph, delivery_locations)
# 总距离 = 2 * (收件阶段权重 + 送件阶段权重)
total_distance = 2 * (pickup_weight + delivery_weight)
return total_distance
# 输出结果
print(solve())
import java.util.*;
public class LogisticsOptimization {
// 全局变量用于存储边权重
private static Map edgeWeights;
/**
* 物流运输最短路径问题求解
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
int n = scanner.nextInt();
int m = scanner.nextInt();
// 构建树的邻接表
Map> graph = new HashMap<>();
for (int i = 1; i <= n; i++) {
graph.put(i, new ArrayList<>());
}
for (int i = 0; i < n - 1; i++) {
int u = scanner.nextInt();
int v = scanner.nextInt();
int c = scanner.nextInt();
graph.get(u).add(new Edge(v, c));
graph.get(v).add(new Edge(u, c));
}
// 收集收件和送件地点
Set pickupLocations = new HashSet<>(); // 收件地点集合
Set deliveryLocations = new HashSet<>(); // 送件地点集合
for (int i = 0; i < m; i++) {
int s = scanner.nextInt();
int t = scanner.nextInt();
pickupLocations.add(s); // s是收件地点
deliveryLocations.add(t); // t是送件地点
}
// 计算收件阶段的Steiner树权重
int pickupWeight = calculateSteinerTreeWeight(graph, pickupLocations);
// 计算送件阶段的Steiner树权重
int deliveryWeight = calculateSteinerTreeWeight(graph, deliveryLocations);
// 总距离 = 2 * (收件阶段权重 + 送件阶段权重)
int totalDistance = 2 * (pickupWeight + deliveryWeight);
System.out.println(totalDistance);
scanner.close();
}
/**
* 计算包含目标节点集合的Steiner树权重
*
* @param graph 邻接表表示的树
* @param targets 目标节点集合
* @return Steiner树的总权重
*/
private static int calculateSteinerTreeWeight(Map> graph, Set targets) {
if (targets.isEmpty()) {
return 0;
}
Set visitedEdges = new HashSet<>();
edgeWeights = new HashMap<>();
// 从节点1开始DFS标记路径
dfsMarkPath(graph, 1, targets, visitedEdges);
// 计算所有标记边的权重总和
return edgeWeights.values().stream().mapToInt(Integer::intValue).sum();
}
/**
* DFS标记从起点到所有目标点路径上的边
*
* @param graph 邻接表表示的树
* @param start 起始节点
* @param targets 目标节点集合
* @param visitedEdges 用于记录访问过的边的集合
* @return 当前子树是否包含目标节点
*/
private static boolean dfsMarkPath(Map> graph, int start,
Set targets, Set visitedEdges) {
// 检查当前节点是否为目标节点
boolean hasTarget = targets.contains(start);
// 遍历所有邻接节点
for (Edge edge : graph.get(start)) {
int neighbor = edge.to;
int weight = edge.weight;
// 创建边的标识符(保证唯一性)
String edgeId = Math.min(start, neighbor) + "-" + Math.max(start, neighbor);
// 避免重复访问边
if (!visitedEdges.contains(edgeId)) {
visitedEdges.add(edgeId);
// 递归DFS邻接节点
if (dfsMarkPath(graph, neighbor, targets, visitedEdges)) {
hasTarget = true;
// 记录这条边及其权重
edgeWeights.put(edgeId, weight);
} else {
// 如果这条路径不通向目标节点,移除标记
visitedEdges.remove(edgeId);
}
}
}
return hasTarget;
}
/**
* 边的数据结构
*/
static class Edge {
int to; // 目标节点
int weight; // 边权重
public Edge(int to, int weight) {
this.to = to;
this.weight = weight;
}
}
}
#include
#include
#include
#include
#include
#include
using namespace std;
// 边的结构体
struct Edge {
int to; // 目标节点
int weight; // 边权重
Edge(int t, int w) : to(t), weight(w) {}
};
// 全局变量用于存储边权重
unordered_map edgeWeights;
/**
* DFS标记从起点到所有目标点路径上的边
*
* @param graph 邻接表表示的树
* @param start 起始节点
* @param targets 目标节点集合
* @param visitedEdges 用于记录访问过的边的集合
* @return 当前子树是否包含目标节点
*/
bool dfsMarkPath(const vector>& graph, int start,
const unordered_set& targets, set& visitedEdges) {
// 检查当前节点是否为目标节点
bool hasTarget = targets.count(start) > 0;
// 遍历所有邻接节点
for (const Edge& edge : graph[start]) {
int neighbor = edge.to;
int weight = edge.weight;
// 创建边的标识符(保证唯一性)
string edgeId = to_string(min(start, neighbor)) + "-" + to_string(max(start, neighbor));
// 避免重复访问边
if (visitedEdges.find(edgeId) == visitedEdges.end()) {
visitedEdges.insert(edgeId);
// 递归DFS邻接节点
if (dfsMarkPath(graph, neighbor, targets, visitedEdges)) {
hasTarget = true;
// 记录这条边及其权重
edgeWeights[edgeId] = weight;
} else {
// 如果这条路径不通向目标节点,移除标记
visitedEdges.erase(edgeId);
}
}
}
return hasTarget;
}
/**
* 计算包含目标节点集合的Steiner树权重
*
* @param graph 邻接表表示的树
* @param targets 目标节点集合
* @return Steiner树的总权重
*/
int calculateSteinerTreeWeight(const vector>& graph, const unordered_set& targets) {
if (targets.empty()) {
return 0;
}
set visitedEdges;
edgeWeights.clear();
// 从节点1开始DFS标记路径
dfsMarkPath(graph, 1, targets, visitedEdges);
// 计算所有标记边的权重总和
int totalWeight = 0;
for (const auto& pair : edgeWeights) {
totalWeight += pair.second;
}
return totalWeight;
}
/**
* 物流运输最短路径问题求解
*/
int main() {
// 读取输入
int n, m;
cin >> n >> m;
// 构建树的邻接表 (1-indexed)
vector> graph(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v, c;
cin >> u >> v >> c;
graph[u].emplace_back(v, c);
graph[v].emplace_back(u, c);
}
// 收集收件和送件地点
unordered_set pickupLocations; // 收件地点集合
unordered_set deliveryLocations; // 送件地点集合
for (int i = 0; i < m; i++) {
int s, t;
cin >> s >> t;
pickupLocations.insert(s); // s是收件地点
deliveryLocations.insert(t); // t是送件地点
}
// 计算收件阶段的Steiner树权重
int pickupWeight = calculateSteinerTreeWeight(graph, pickupLocations);
// 计算送件阶段的Steiner树权重
int deliveryWeight = calculateSteinerTreeWeight(graph, deliveryLocations);
// 总距离 = 2 * (收件阶段权重 + 送件阶段权重)
int totalDistance = 2 * (pickupWeight + deliveryWeight);
cout << totalDistance << endl;
return 0;
}
/**
* 物流运输最短路径问题求解
*/
// 边的类
class Edge {
/**
* @param {number} to 目标节点
* @param {number} weight 边权重
*/
constructor(to, weight) {
this.to = to;
this.weight = weight;
}
}
// 全局变量用于存储边权重
let edgeWeights = new Map();
/**
* DFS标记从起点到所有目标点路径上的边
*
* @param {Array>} graph 邻接表表示的树
* @param {number} start 起始节点
* @param {Set} targets 目标节点集合
* @param {Set} visitedEdges 用于记录访问过的边的集合
* @returns {boolean} 当前子树是否包含目标节点
*/
function dfsMarkPath(graph, start, targets, visitedEdges) {
// 检查当前节点是否为目标节点
let hasTarget = targets.has(start);
// 遍历所有邻接节点
for (const edge of graph[start]) {
const neighbor = edge.to;
const weight = edge.weight;
// 创建边的标识符(保证唯一性)
const edgeId = Math.min(start, neighbor) + "-" + Math.max(start, neighbor);
// 避免重复访问边
if (!visitedEdges.has(edgeId)) {
visitedEdges.add(edgeId);
// 递归DFS邻接节点
if (dfsMarkPath(graph, neighbor, targets, visitedEdges)) {
hasTarget = true;
// 记录这条边及其权重
edgeWeights.set(edgeId, weight);
} else {
// 如果这条路径不通向目标节点,移除标记
visitedEdges.delete(edgeId);
}
}
}
return hasTarget;
}
/**
* 计算包含目标节点集合的Steiner树权重
*
* @param {Array>} graph 邻接表表示的树
* @param {Set} targets 目标节点集合
* @returns {number} Steiner树的总权重
*/
function calculateSteinerTreeWeight(graph, targets) {
if (targets.size === 0) {
return 0;
}
const visitedEdges = new Set();
edgeWeights.clear();
// 从节点1开始DFS标记路径
dfsMarkPath(graph, 1, targets, visitedEdges);
// 计算所有标记边的权重总和
let totalWeight = 0;
for (const weight of edgeWeights.values()) {
totalWeight += weight;
}
return totalWeight;
}
/**
* 主函数:处理输入输出
*/
function solve() {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let lineCount = 0;
let n, m;
let graph;
let pickupLocations, deliveryLocations;
let edgeInputCount = 0;
let taskInputCount = 0;
rl.on('line', (line) => {
if (lineCount === 0) {
// 读取第一行:地点数和任务数
[n, m] = line.split(' ').map(Number);
// 初始化图的邻接表 (1-indexed)
graph = new Array(n + 1);
for (let i = 0; i <= n; i++) {
graph[i] = [];
}
// 初始化地点集合
pickupLocations = new Set(); // 收件地点集合
deliveryLocations = new Set(); // 送件地点集合
lineCount++;
} else if (edgeInputCount < n - 1) {
// 读取边的信息
const [u, v, c] = line.split(' ').map(Number);
graph[u].push(new Edge(v, c));
graph[v].push(new Edge(u, c));
edgeInputCount++;
} else if (taskInputCount < m) {
// 读取任务信息
const [s, t] = line.split(' ').map(Number);
pickupLocations.add(s); // s是收件地点
deliveryLocations.add(t); // t是送件地点
taskInputCount++;
// 如果所有任务都读取完毕,开始计算
if (taskInputCount === m) {
// 计算收件阶段的Steiner树权重
const pickupWeight = calculateSteinerTreeWeight(graph, pickupLocations);
// 计算送件阶段的Steiner树权重
const deliveryWeight = calculateSteinerTreeWeight(graph, deliveryLocations);
// 总距离 = 2 * (收件阶段权重 + 送件阶段权重)
const totalDistance = 2 * (pickupWeight + deliveryWeight);
console.log(totalDistance);
rl.close();
}
}
});
}
// 运行求解函数
solve();
#include
#include
#include
#include
#define MAX_N 100005
#define MAX_M 100005
// 边的结构体
struct Edge {
int to; // 目标节点
int weight; // 边权重
struct Edge* next; // 链表指针
};
// 图的邻接表
struct Edge* graph[MAX_N];
// 边权重映射(简化实现,使用数组存储)
int edgeWeights[MAX_N * 2];
int edgeCount;
// 目标节点集合(使用数组模拟)
bool isPickupLocation[MAX_N];
bool isDeliveryLocation[MAX_N];
/**
* 添加边到邻接表
*
* @param from 起始节点
* @param to 目标节点
* @param weight 边权重
*/
void addEdge(int from, int to, int weight) {
struct Edge* newEdge = (struct Edge*)malloc(sizeof(struct Edge));
newEdge->to = to;
newEdge->weight = weight;
newEdge->next = graph[from];
graph[from] = newEdge;
}
/**
* DFS标记从起点到所有目标点路径上的边
*
* @param start 起始节点
* @param targets 目标节点数组(用bool数组表示)
* @param visited 访问标记数组
* @return 当前子树是否包含目标节点
*/
bool dfsMarkPath(int start, bool* targets, bool* visited) {
visited[start] = true;
// 检查当前节点是否为目标节点
bool hasTarget = targets[start];
// 遍历所有邻接节点
struct Edge* edge = graph[start];
while (edge != NULL) {
int neighbor = edge->to;
int weight = edge->weight;
// 如果邻接节点未被访问
if (!visited[neighbor]) {
// 递归DFS邻接节点
if (dfsMarkPath(neighbor, targets, visited)) {
hasTarget = true;
// 记录这条边的权重
edgeWeights[edgeCount++] = weight;
}
}
edge = edge->next;
}
return hasTarget;
}
/**
* 计算包含目标节点集合的Steiner树权重
*
* @param n 节点总数
* @param targets 目标节点数组(用bool数组表示)
* @return Steiner树的总权重
*/
int calculateSteinerTreeWeight(int n, bool* targets) {
// 检查是否有目标节点
bool hasAnyTarget = false;
for (int i = 1; i <= n; i++) {
if (targets[i]) {
hasAnyTarget = true;
break;
}
}
if (!hasAnyTarget) {
return 0;
}
// 初始化访问数组
bool visited[MAX_N];
memset(visited, false, sizeof(visited));
// 重置边计数
edgeCount = 0;
// 从节点1开始DFS标记路径
dfsMarkPath(1, targets, visited);
// 计算所有标记边的权重总和
int totalWeight = 0;
for (int i = 0; i < edgeCount; i++) {
totalWeight += edgeWeights[i];
}
return totalWeight;
}
/**
* 物流运输最短路径问题求解
*/
int main() {
int n, m;
// 读取输入
scanf("%d %d", &n, &m);
// 初始化图
memset(graph, 0, sizeof(graph));
memset(isPickupLocation, false, sizeof(isPickupLocation));
memset(isDeliveryLocation, false, sizeof(isDeliveryLocation));
// 读取边的信息
for (int i = 0; i < n - 1; i++) {
int u, v, c;
scanf("%d %d %d", &u, &v, &c);
addEdge(u, v, c);
addEdge(v, u, c);
}
// 读取任务信息,收集收件和送件地点
for (int i = 0; i < m; i++) {
int s, t;
scanf("%d %d", &s, &t);
isPickupLocation[s] = true; // s是收件地点
isDeliveryLocation[t] = true; // t是送件地点
}
// 计算收件阶段的Steiner树权重
int pickupWeight = calculateSteinerTreeWeight(n, isPickupLocation);
// 计算送件阶段的Steiner树权重
int deliveryWeight = calculateSteinerTreeWeight(n, isDeliveryLocation);
// 总距离 = 2 * (收件阶段权重 + 送件阶段权重)
int totalDistance = 2 * (pickupWeight + deliveryWeight);
printf("%d\n", totalDistance);
// 释放内存
for (int i = 1; i <= n; i++) {
struct Edge* edge = graph[i];
while (edge != NULL) {
struct Edge* temp = edge;
edge = edge->next;
free(temp);
}
}
return 0;
}
【华为od机试真题Python+JS+Java合集】【超值优惠】:Py/JS/Java合集
【华为od机试真题Python】:Python真题题库
【华为od机试真题JavaScript】:JavaScript真题题库
【华为od机试真题Java】:Java真题题库
【华为od机试真题C++】:C++真题题库
【华为od机试真题C语言】:C语言真题题库
【华为od面试手撕代码题库】:面试手撕代码题库
【华为校招&实习机试面试交流群:1048120678】