问题描述

2023 年 NOI 最后一题是一道融合图论与动态规划的综合优化问题,聚焦于带时间窗约束的多路径规划。题目具体要求如下:

给定一个有向图,其中节点代表城市,边代表交通路线。每条边具有三个属性:行驶时间、基础费用和容量限制。需要将 K 批货物从起点 S 运输到终点 T,每批货物有各自的时间窗要求(必须在 [earliest_i, latest_i] 时间区间内送达)。同时,每条边在不同时间段有不同的拥堵系数,会影响实际行驶时间。请设计算法找到满足所有货物时间窗约束且总费用最小的运输方案。

问题分析

本题是经典路径规划问题的扩展,核心挑战包括:

  1. 多约束条件:需同时满足各批货物的时间窗约束和边的容量限制
  2. 时间依赖性:边的实际行驶时间随出发时段动态变化
  3. 多任务规划:需要为 K 批货物规划路径,考虑路径间的资源竞争
  4. 费用优化:在满足所有约束的前提下最小化总运输费用

问题可转化为带时间窗的多商品流问题,需要结合动态规划、图论和调度算法的思想进行求解。

算法设计

我们采用基于时间扩展网络的动态规划方法,结合改进的 Dijkstra 算法:

  1. 时间扩展网络构建:将每个节点按时间片拆分,形成新的时间扩展图,使时间依赖的边权重转化为静态权重
  2. 状态表示:定义 dp [k][u][t] 为第 k 批货物到达节点 u 时时间为 t 的最小累计费用
  3. 容量约束处理:对每条边的使用次数进行计数,超过容量限制则不可再使用
  4. 路径规划:对每批货物,在时间扩展网络中寻找满足其时间窗约束的最小费用路径
  5. 冲突解决:若多条路径使用同一条边导致容量超限,采用费用补偿机制调整路径选择
实现细节
  1. 时间离散化:将连续时间划分为离散时间片,简化时间窗处理
  2. 拥堵系数模型:实现基于时间段的拥堵计算函数,反映实际行驶时间的动态变化
  3. 状态压缩:对相同货物、节点和时间的状态,仅保留最小费用记录
  4. 容量管理:使用二维数组记录每条边在各时间片的使用次数,确保不超过容量限制
  5. 时间窗检查:对每批货物的路径严格验证到达终点时间是否在 [earliest_i, latest_i] 区间内
复杂度分析
  • 时间复杂度:O (K × T × (V + E) × log (V × T)),其中 K 为货物批次数,T 为总时间片数量,V 为节点数,E 为边数
  • 空间复杂度:O (K × V × T + E × T),主要用于存储动态规划状态和边容量使用记录

通过合理设置时间片粒度和状态剪枝策略,算法能够在题目给定的约束范围内高效运行。

代码实现

以下是英文版的 C++ 实现:

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <algorithm>
#include <cmath>using namespace std;const int MAX_TIME = 1000;  // Maximum possible time (discretized)
const int INF = INT_MAX / 2; // To avoid overflow// Structure to represent an edge in original graph
struct Edge {int to;               // Target nodeint base_time;        // Base travel timeint base_cost;        // Base costint capacity;         // Maximum number of usesint current_usage;    // Current usage count (for capacity management)Edge(int t, int bt, int bc, int cap) : to(t), base_time(bt), base_cost(bc), capacity(cap), current_usage(0) {}
};// Structure to represent a state in priority queue
struct State {int node;     // Current nodeint time;     // Current timeint cost;     // Accumulated costState(int n, int t, int c) : node(n), time(t), cost(c) {}// For priority queue (min-heap based on cost)bool operator>(const State& other) const {return cost > other.cost;}
};// Structure to represent a shipment
struct Shipment {int id;               // Shipment identifierint earliest;         // Earliest delivery timeint latest;           // Latest delivery timevector<int> path;     // Optimal path foundint arrival_time;     // Arrival time at destinationShipment(int i, int e, int l) : id(i), earliest(e), latest(l), arrival_time(-1) {}
};// Calculate time-dependent travel time considering congestion
int get_actual_time(int base_time, int departure_time) {int hour = departure_time % 24;double congestion = 1.0;// Morning rush hour (7:00-9:59) - 50% congestionif (hour >= 7 && hour < 10) {congestion = 1.5;}// Evening rush hour (17:00-19:59) - 60% congestionelse if (hour >= 17 && hour < 20) {congestion = 1.6;}// Late night (23:00-5:59) - 30% less congestionelse if (hour >= 23 || hour < 6) {congestion = 0.7;}return static_cast<int>(ceil(base_time * congestion));
}// Find optimal path for a single shipment using modified Dijkstra
bool find_shipment_path(int S, int T, const Shipment& shipment,const vector<vector<Edge>>& original_edges,vector<vector<vector<int>>>& usage,  // usage[u][v][t] = number of times edge u->v is used at time tvector<int>& path, int& arrival_time
) {int n = original_edges.size() - 1;  // Nodes are 1-indexedint earliest = shipment.earliest;int latest = shipment.latest;// DP table: dp[node][time] = minimum cost to reach node at timevector<vector<int>> dp(n + 1, vector<int>(MAX_TIME + 1, INF));// Previous node and time for path reconstructionvector<vector<pair<int, int>>> prev(n + 1, vector<pair<int, int>>(MAX_TIME + 1, {-1, -1}));// Priority queue for Dijkstra's algorithmpriority_queue<State, vector<State>, greater<State>> pq;// Initialize starting nodedp[S][0] = 0;pq.emplace(S, 0, 0);// Track best arrival time and costint best_cost = INF;int best_time = -1;while (!pq.empty()) {State current = pq.top();pq.pop();int u = current.node;int t = current.time;int c = current.cost;// If we've reached the target within time windowif (u == T) {if (t >= earliest && t <= latest && c < best_cost) {best_cost = c;best_time = t;}continue;  // Continue searching for better options}// Skip if we've found a better path to this stateif (c > dp[u][t]) {continue;}// Explore all neighboring edgesfor (const Edge& edge : original_edges[u]) {int v = edge.to;int actual_time = get_actual_time(edge.base_time, t);int arrival_t = t + actual_time;// Check if arrival time exceeds maximum allowedif (arrival_t > MAX_TIME) {continue;}// Check edge capacity at this departure timeif (usage[u][v][t] >= edge.capacity) {continue;}// Calculate cost for this edgeint edge_cost = edge.base_cost;// Higher cost during peak hoursint hour = t % 24;if ((hour >= 7 && hour < 10) || (hour >= 17 && hour < 20)) {edge_cost = static_cast<int>(edge_cost * 1.2);}int new_cost = c + edge_cost;// Update state if this path is betterif (new_cost < dp[v][arrival_t]) {dp[v][arrival_t] = new_cost;prev[v][arrival_t] = {u, t};pq.emplace(v, arrival_t, new_cost);}}}// If no valid path foundif (best_time == -1) {return false;}// Reconstruct patharrival_time = best_time;int curr_node = T;int curr_time = best_time;while (curr_node != S) {path.push_back(curr_node);auto [prev_node, prev_time] = prev[curr_node][curr_time];if (prev_node == -1) {  // Should not happen if path existsreturn false;}// Mark edge usageusage[prev_node][curr_node][prev_time]++;curr_node = prev_node;curr_time = prev_time;}path.push_back(S);reverse(path.begin(), path.end());return true;
}int main() {int n, m, K;          // Number of nodes, edges, and shipmentsint S, T;             // Start and target nodes// Read inputcin >> n >> m >> K;cin >> S >> T;// Build original adjacency listvector<vector<Edge>> original_edges(n + 1);  // 1-indexedfor (int i = 0; i < m; ++i) {int u, v, t, c, cap;cin >> u >> v >> t >> c >> cap;original_edges[u].emplace_back(v, t, c, cap);}// Read shipmentsvector<Shipment> shipments;for (int i = 0; i < K; ++i) {int earliest, latest;cin >> earliest >> latest;shipments.emplace_back(i, earliest, latest);}// Initialize edge usage tracking: usage[u][v][t]vector<vector<vector<int>>> usage(n + 1, vector<vector<int>>(n + 1, vector<int>(MAX_TIME + 1, 0)));// Process each shipmentint total_cost = 0;bool possible = true;for (auto& shipment : shipments) {vector<int> path;int arrival_time;if (!find_shipment_path(S, T, shipment, original_edges, usage, path, arrival_time)) {possible = false;break;}shipment.path = path;shipment.arrival_time = arrival_time;// Calculate actual cost for this shipmentint cost = 0;for (int i = 0; i < path.size() - 1; ++i) {int u = path[i];int v = path[i + 1];// Find the edge to get its costfor (const Edge& e : original_edges[u]) {if (e.to == v) {// Calculate cost based on departure time (simplified)cost += e.base_cost;break;}}}total_cost += cost;}// Output resultsif (!possible) {cout << -1 << endl;  // No valid solution exists} else {cout << total_cost << endl;// Output paths for each shipment (as required by problem statement)for (const auto& shipment : shipments) {for (int node : shipment.path) {cout << node << " ";}cout << endl;}}return 0;
}
代码解析

上述代码实现了针对 2023 年 NOI 最后一题的完整解决方案,主要包含以下核心部分:

  1. 数据结构设计

    • Edge结构体存储边的基础信息,包括容量限制和当前使用次数
    • State结构体表示优先队列中的状态,用于 Dijkstra 算法
    • Shipment结构体记录每批货物的时间窗约束和规划结果
  2. 时间依赖模型

    • get_actual_time函数根据出发时间计算实际行驶时间,模拟早高峰(7:00-9:59)、晚高峰(17:00-19:59)和深夜(23:00-5:59)的拥堵情况
    • 高峰时段费用上浮 20%,体现实际运输成本的动态变化
  3. 核心算法实现

    • find_shipment_path函数为单批货物寻找最优路径,使用改进的 Dijkstra 算法
    • 二维 DP 数组dp[node][time]记录到达节点的最小费用,结合优先队列实现高效搜索
    • 三维数组usage[u][v][t]跟踪边的使用情况,确保不超过容量限制
  4. 多货物处理

    • 按顺序为每批货物规划路径,已使用的边容量会影响后续货物的路径选择
    • 严格检查每批货物的到达时间是否在其时间窗 [earliest_i, latest_i] 内
  5. 结果输出

    • 若所有货物都能找到满足约束的路径,则输出总费用和各货物的路径
    • 若任何一批货物无法找到有效路径,则输出 - 1 表示无解

该算法通过时间扩展网络和动态规划的结合,有效处理了时间依赖、容量限制和多货物时间窗等多重约束,在满足所有条件的前提下找到了总费用最小的运输方案。

扩展思考

本题可从以下方向进一步扩展:

  1. 引入货物优先级机制,高优先级货物可优先使用容量有限的边
  2. 考虑边的随机故障概率,设计鲁棒性更强的路径规划方案
  3. 扩展为多目标优化,在费用、时间和可靠性之间寻找平衡

这些扩展更贴近实际物流场景,对算法的灵活性和适应性提出了更高要求。

通过本题的求解可以看出,NOI 题目越来越注重实际问题的抽象与建模能力,要求选手不仅掌握基础算法,还要能够将其灵活应用于复杂的约束优化场景,体现了算法竞赛与实际应用的紧密结合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93566.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93566.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93566.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android补全计划 TextView设置文字不同字体和颜色

1 富文本 1 java中动态加载文本 颜色 String strMsg "今天<font color\"#00ff00\">天气不错</font>"; tv_msg.setText(Html.fromHtml(strMsg));字体和颜色 String str2 "今天<font color\"#00ff00\"><big>天气不…

C语言:详解单链表与例题

C语言&#xff1a;详解单链表与例题 1.单链表的实现 2.例题&#xff1a;移除链表元素 1.单链表的实现 链表根据带头或不带头、单向或双向、循环或不循环分类为8种&#xff0c;最常用的是单链表和双向链表&#xff0c;单链表是 不带头单向不循环 链表。 链表由节点组成&#xff…

从0开始学习R语言--Day62--RE插补

对于会有多次测量值的数据&#xff0c;用普通的回归去插补&#xff0c;往往会忽略掉数据个体本身的特点&#xff0c;毕竟多次的测量值其实就代表了数据个体的不稳定性&#xff0c;存在额外的干扰。而RE的插补原理是结合个体本身的随机效应和群体的固体效应再加上截距进行插补的…

RESTful API开发指南:使用Spring Boot构建企业级接口

目录 1. 引言2. RESTful API基础概念3. Spring Boot环境搭建4. 项目结构设计5. 核心组件开发6. 数据库集成7. 安全认证8. 异常处理9. API文档生成10. 测试策略11. 部署与监控12. 最佳实践 1. 引言 在现代软件开发中&#xff0c;RESTful API已成为构建分布式系统和微服务架构…

从 Print 到 Debug:用 PyCharm 掌控复杂程序的调试之道

目录摘要调试工具窗口会话工具栏调试工具栏单步工具栏调试器选项卡调用栈帧&#xff08;Frames&#xff09;变量&#xff08;Variables&#xff09;&#x1f4a1; 表达式求值区域&#xff08;Evaluate expression field&#xff09;&#x1f5b1;️ 右键菜单&#xff08;Contex…

用于前列腺活检分级的分层视觉 Transformer:迈向弥合泛化差距|文献速递-医学影像算法文献分享

Title题目Hierarchical Vision Transformers for prostate biopsy grading: Towardsbridging the generalization gap用于前列腺活检分级的分层视觉 Transformer&#xff1a;迈向弥合泛化差距01文献速递介绍前列腺癌是全球男性中第二常见的确诊癌症&#xff0c;也是第五大致命癌…

Apple基础(Xcode②-Flutter结构解析)

&#x1f3d7;️ 目录结构速查表&#xff08;your_project/ios/ 下&#xff09;ios/ ├── Runner/ ← 原生 iOS 工程根目录&#xff08;Xcode 打开它&#xff09; │ ├── AppDelegate.swift ← App 入口&#xff08;类似 Android 的 MainActivity&…

X00229-基于深度强化学习的车联网资源分配python完整

X00229-基于深度强化学习的车联网资源分配python完整

面向多模态自监督学习的共享表示与独有表示解耦

通俗说法&#xff1a;在多模态自监督学习中&#xff0c;将共享信息和独有信息分离开来 Abstract 问题&#xff1a; 传统方法通常假设在训练和推理阶段都可以访问所有模态信息&#xff0c;这在实际应用中面对模态不完整输入时会导致性能显著下降。 解决方法&#xff1a;提出了一…

【iOS】weak修饰符

前言前面我们已经学习了解了sideTable&#xff0c;今天来看看在OC中&#xff0c;sideTable是如何在我们使用weak时工作的。在OC中&#xff0c;weak修饰符是一种用于声明“弱引用”的关键字&#xff0c;其核心特性是不参与对象的引用计数管理&#xff0c;而且当被引用的对象被释…

【JVM篇10】:三种垃圾回收算法对比详解

文章目录1. 标记-清除算法2. 复制算法3. 标记-整理算法总结与面试要点在通过 可达性分析等算法识别出所有存活对象和垃圾对象后&#xff0c;垃圾收集器&#xff08;GC&#xff1a;Garbage Collector&#xff09;就需要执行回收操作来释放垃圾对象所占用的内存。以下是三种最基础…

JXD进步25.7.30

1.为啥是update&#xff0c;因为你if判断有问题。或者是你上来就给id赋值了。2. 这个是清空network历史3.断点位置打在这里&#xff1a;打在上面它进不来4.

Flutter开发实战之网络请求与数据处理

第6章:网络请求与数据处理 “数据是应用的血液,网络是连接世界的桥梁。” 在移动应用开发中,与服务器进行数据交互是必不可少的功能。无论是获取用户信息、提交表单数据,还是上传图片、下载文件,都离不开网络请求。本章将带你深入掌握Flutter中的网络编程技巧。 6.1 网络…

快速分页实现热点功能-索引和order by

需求:分页求出进三天的发布视频的权重热度 权重 / 衰减时间 衰减时间 当前时间 - 视频发布时间 小根堆来实现这个公式可以很好的利用半衰期来进行解决难点:如果一次性加载太多到springBoot服务器里面会造成堆内存占用过多&#xff0c;分页又有可能造成深分页问题&#xff0c;…

HAProxy(高可用性代理)

1 HAProxy 简介 HAProxy&#xff08; High Availability Proxy&#xff09;是一个高性能的负载均衡器和代理服务器&#xff0c;为基于 TCP 和 HTTP 的应用程序提供高可用性、负载平衡和代理&#xff0c;广泛应用于提高 web 应用程序的性能和可靠性。它支持多种协议&#xff0c…

Vulnhub靶场:ica1

一、信息收集nmap扫描一下IP。&#xff08;扫不出来的可以看一下前面几篇找ip的步骤&#xff09;下面给了框架的版本是9.2的&#xff0c;我们去kali里搜一下有没有已经公开的漏洞。searchsploit qdPM 9.2 locate 50176.txt more /usr/share/exploitdb/exploits/php/webapps/50…

【Dv3admin】ORM数据库无法查询的问题

Django 运行过程中&#xff0c;数据库连接的健康状态直接影响应用的稳定性和数据访问准确性。长时间空闲的数据库连接经常因外部机制被回收&#xff0c;进而引发数据查询异常和返回无效结果。 本文围绕 Django 中数据库连接长时间空闲导致的连接失效问题&#xff0c;介绍相关的…

使用 Flownex 对机械呼吸机进行建模

当患者无法独立呼吸时&#xff0c;机械呼吸机通过气管插管将富氧空气输送到患者的肺部。肺是敏感而复杂的器官&#xff0c;因此在无法忍受的压力和体积范围内提供空气&#xff0c;根据每分钟所需的呼吸次数计时&#xff0c;并适当加湿和加热。机械呼吸机的精确建模对于其安全有…

力扣刷题日常(7-8)

力扣刷题日常(7-8) 第7题: 整数反转(难度: 中等) 原题: 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果. 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0. 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;.…

串口接收数据包(协议带帧头帧尾)的编程实现方法:1、数据包格式定义结构体2、使用队列进行数据接收、校验解包

这种带帧头帧尾的数据包处理流程可以简单概括为 “识别边界→提取有效数据→验证完整性” 三个核心步骤&#xff0c;具体操作如下&#xff1a;1. 数据包格式定义&#xff08;先约定规则&#xff09;首先明确一个 “合格数据包” 的结构&#xff0c;比如&#xff1a; 帧头&#…