2023 年 NOI 最后一題題解

問題描述

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; 幀頭&#…