2020 年 NOI 最后一題題解

問題描述

2020 年 NOI 最后一題是一道結合圖論、動態規劃與狀態壓縮的綜合性算法題,題目圍繞 "疫情期間的物資配送" 展開,具體要求如下:

給定一個有向圖 G (V, E),其中節點代表城市,邊代表連接城市的道路。每個節點有兩個屬性:風險等級 l(0-5)和防疫檢查時間 t。每條邊有三個屬性:行駛時間 d、基礎成本 c 和最大每日通行量 k。

需要將一批應急物資從起點 S 運往終點 T,滿足以下約束:

  1. 每日 0 點至 6 點為宵禁時間,禁止通行(邊無法使用)
  2. 經過風險等級為 l 的節點需要額外隔離時間 l×2 小時
  3. 每條邊每天的使用次數不能超過其最大通行量 k
  4. 總運輸時間不得超過 D 天(按自然日計算)

請設計算法找到滿足所有約束條件的最小成本運輸方案,若存在多條路徑則選擇總運輸時間最短的方案。

問題分析

本題的核心挑戰在于時間與空間約束的復雜交互,傳統路徑算法需要進行針對性擴展:

  1. 時間周期性約束:宵禁制度引入了以天為周期的時間限制,影響邊的可用性
  2. 節點風險成本:不同風險等級的節點會帶來額外隔離時間,增加了狀態維度
  3. 資源容量限制:邊的每日通行量限制要求跟蹤時間與使用次數的關系
  4. 多目標優化:首要目標是最小化成本,次要目標是縮短運輸時間

問題可轉化為帶周期性時間約束和容量限制的最小成本路徑問題,需要通過時間擴展網絡來建模周期性約束。

算法設計

我們采用基于時間擴展網絡的改進 Dijkstra 算法,結合動態規劃處理周期性約束:

  1. 狀態表示:定義 dp [u][d][t] 為第 d 天的 t 時刻到達節點 u 時的最小成本,其中:

    • u 為當前節點
    • d 為當前天數(從 0 開始)
    • t 為當天時刻(0-23 小時)
  2. 狀態轉移:對于每個狀態 (u, d, t),考慮兩種決策:

    • 在節點停留:停留 Δt 時間后的新狀態為 (u, d', t'),其中 d' 和 t' 根據跨天情況計算
    • 前往相鄰節點:使用邊 (u, v),需檢查是否在宵禁時間,計算到達 v 的天數和時刻,更新成本
  3. 約束處理:

    • 宵禁檢查:邊只能在 6-24 點使用(非宵禁時段)
    • 容量限制:每條邊按天跟蹤使用次數,不超過最大通行量 k
    • 風險隔離:到達節點 v 后需增加 l_v×2 小時的隔離時間
    • 時間上限:總天數 d 不得超過 D
  4. 優先級隊列:按成本排序,成本相同則按總時間(天數 + 時刻)排序

實現細節
  1. 時間建模:將時間分解為 "天數 + 時刻" 的組合形式,方便處理跨天和周期性約束
  2. 狀態剪枝:對于相同節點、天數和時刻,僅保留最小成本狀態
  3. 容量跟蹤:使用三維數組 count [u][v][d] 記錄邊 (u, v) 在第 d 天的使用次數
  4. 隔離處理:到達節點后立即計算并累加隔離時間,更新狀態時間
  5. 路徑重構:通過前驅指針記錄每個狀態的來源,包括使用的邊和停留決策
復雜度分析
  • 時間復雜度:O (E × D × 24 × log (V × D × 24)),其中 D 為最大天數,V 為節點數,E 為邊數
  • 空間復雜度:O (V × D × 24 + E × D),主要用于存儲 DP 狀態和邊的每日使用計數

通過合理設置天數上限 D(題目通常限制在 10 天以內),算法能夠在規定時間內高效運行。

代碼實現

以下是英文版的 C++ 實現:

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <algorithm>using namespace std;const int MAX_NODES = 505;
const int MAX_DAYS = 15;    // Maximum allowed days
const int HOURS_PER_DAY = 24;
const int CURFEW_START = 0;  // 0:00
const int CURFEW_END = 6;    // 6:00 (exclusive)
const int INF_COST = INT_MAX / 2;// Structure to represent an edge
struct Edge {int to;             // Target nodeint duration;       // Travel time in hoursint cost;           // Transportation costint capacity;       // Maximum daily capacityEdge(int t, int d, int c, int cap): to(t), duration(d), cost(c), capacity(cap) {}
};// Structure to represent a node
struct Node {int risk_level;     // Risk level (0-5)int check_time;     // Check time in hours
};// Structure to represent a state in priority queue
struct State {int node;           // Current nodeint day;            // Current dayint hour;           // Current hour (0-23)int cost;           // Accumulated costState(int n, int d, int h, int c): node(n), day(d), hour(h), cost(c) {}// For priority queue (min-heap based on cost, then day, then hour)bool operator>(const State& other) const {if (cost != other.cost) {return cost > other.cost;}if (day != other.day) {return day > other.day;}return hour > other.hour;}
};// Structure to store DP state information
struct DPState {int cost;           // Minimum cost to reach this stateint prev_node;      // Previous nodeint prev_day;       // Previous dayint prev_hour;      // Previous hourint via_edge;       // Index of edge used to reach here (-1 if stayed)DPState() : cost(INF_COST), prev_node(-1), prev_day(-1), prev_hour(-1), via_edge(-1) {}
};// Helper function to add time and handle day transitions
void add_time(int& day, int& hour, int add_hours) {hour += add_hours;while (hour >= HOURS_PER_DAY) {hour -= HOURS_PER_DAY;day++;}
}int main() {int n, m;               // Number of nodes and edgesint S, T, D_max;        // Start node, target node, maximum allowed days// Read inputcin >> n >> m;cin >> S >> T >> D_max;// Initialize nodesvector<Node> nodes(n + 1);  // 1-indexedfor (int i = 1; i <= n; ++i) {cin >> nodes[i].risk_level >> nodes[i].check_time;}// Read edgesvector<vector<Edge>> edges(n + 1);vector<vector<vector<int>>> edge_usage(n + 1, vector<vector<int>>(n + 1, vector<int>(MAX_DAYS, 0)));  // [u][v][day] = usage countfor (int i = 0; i < m; ++i) {int u, v, d, c, cap;cin >> u >> v >> d >> c >> cap;edges[u].emplace_back(v, d, c, cap);}// DP table: dp[node][day][hour] = best statevector<vector<vector<DPState>>> dp(n + 1, vector<vector<DPState>>(MAX_DAYS, vector<DPState>(HOURS_PER_DAY)));// Priority queue for modified Dijkstra's algorithmpriority_queue<State, vector<State>, greater<State>> pq;// Initialize starting node (day 0, assuming we start at 6:00 to avoid curfew)int start_hour = 6;  // Start at 6:00 to avoid curfewdp[S][0][start_hour].cost = 0;pq.emplace(S, 0, start_hour, 0);// Track best solutionint best_cost = INF_COST;int best_day = -1;int best_hour = -1;// Process stateswhile (!pq.empty()) {State current = pq.top();pq.pop();int u = current.node;int day = current.day;int hour = current.hour;int cost = current.cost;// Check if we've exceeded maximum daysif (day >= D_max) {continue;}// Skip if we've found a better stateif (cost > dp[u][day][hour].cost) {continue;}// Check if we've reached targetif (u == T) {if (cost < best_cost) {best_cost = cost;best_day = day;best_hour = hour;}continue;}// Option 1: Stay at current node for 1 hour (can be extended by staying multiple times)int new_day = day;int new_hour = hour + 1;if (new_hour >= HOURS_PER_DAY) {new_hour -= HOURS_PER_DAY;new_day++;}if (new_day < MAX_DAYS && cost < dp[u][new_day][new_hour].cost) {dp[u][new_day][new_hour].cost = cost;dp[u][new_day][new_hour].prev_node = u;dp[u][new_day][new_hour].prev_day = day;dp[u][new_day][new_hour].prev_hour = hour;dp[u][new_day][new_hour].via_edge = -1;  // Indicates stayingpq.emplace(u, new_day, new_hour, cost);}// Option 2: Travel to adjacent nodes via edgesfor (size_t i = 0; i < edges[u].size(); ++i) {const Edge& edge = edges[u][i];int v = edge.to;int travel_time = edge.duration;int edge_cost = edge.cost;// Check if current time is within curfew (cannot travel during curfew)if (hour >= CURFEW_START && hour < CURFEW_END) {continue;}// Check if we would arrive during curfew (allowed, but departure must be outside)// Calculate arrival timeint arrival_day = day;int arrival_hour = hour + travel_time;// Handle day transitionswhile (arrival_hour >= HOURS_PER_DAY) {arrival_hour -= HOURS_PER_DAY;arrival_day++;}// Check if we exceed maximum daysif (arrival_day >= D_max) {continue;}// Check edge capacity for current dayif (edge_usage[u][v][day] >= edge.capacity) {continue;}// Calculate total time including check and quarantine at destinationint total_additional_time = nodes[v].check_time + nodes[v].risk_level * 2;int final_day = arrival_day;int final_hour = arrival_hour;add_time(final_day, final_hour, total_additional_time);if (final_day >= D_max) {continue;}// Calculate new costint new_cost = cost + edge_cost;// Update edge usage (temporarily)edge_usage[u][v][day]++;// Update state if this path is betterif (new_cost < dp[v][final_day][final_hour].cost) {dp[v][final_day][final_hour].cost = new_cost;dp[v][final_day][final_hour].prev_node = u;dp[v][final_day][final_hour].prev_day = day;dp[v][final_day][final_hour].prev_hour = hour;dp[v][final_day][final_hour].via_edge = i;pq.emplace(v, final_day, final_hour, new_cost);} else {// If not better, rollback edge usageedge_usage[u][v][day]--;}}}// Check if solution existsif (best_cost == INF_COST) {cout << -1 << endl;return 0;}// Reconstruct pathvector<int> path;int curr_node = T;int curr_day = best_day;int curr_hour = best_hour;while (curr_node != -1) {path.push_back(curr_node);const DPState& state = dp[curr_node][curr_day][curr_hour];int next_node = state.prev_node;int next_day = state.prev_day;int next_hour = state.prev_hour;curr_node = next_node;curr_day = next_day;curr_hour = next_hour;}reverse(path.begin(), path.end());// Output resultscout << best_cost << " " << best_day << " " << best_hour << endl;for (size_t i = 0; i < path.size(); ++i) {if (i > 0) cout << " -> ";cout << path[i];}cout << endl;return 0;
}
代碼解析

上述代碼實現了針對 2020 年 NOI 最后一題的完整解決方案,主要包含以下核心部分:

  1. 數據結構設計

    • Edge結構體存儲邊的行駛時間、成本和每日最大通行量
    • Node結構體記錄節點的風險等級和防疫檢查時間
    • State結構體表示優先隊列中的狀態,包含當前節點、天數、時刻和累計成本
    • DPState結構體存儲動態規劃狀態信息,包括成本、前驅節點和到達方式
  2. 核心算法實現

    • 采用改進的 Dijkstra 算法,使用優先級隊列按成本、天數和時刻排序處理狀態
    • 三維 DP 數組dp[node][day][hour]跟蹤到達節點的最優狀態
    • 實現兩種狀態轉移:在當前節點停留,或通過邊前往相鄰節點
  3. 約束處理機制

    • 宵禁檢查:確保僅在 6-24 點使用邊進行運輸
    • 容量管理:通過edge_usage數組跟蹤每條邊每天的使用次數
    • 風險隔離:到達節點后自動計算并累加檢查時間和隔離時間(風險等級 ×2)
    • 時間控制:嚴格限制總天數不超過 D_max
  4. 時間計算

    • add_time輔助函數處理時間累加和跨天計算
    • 將時間分解為 "天數 + 時刻" 的組合形式,方便處理周期性約束
  5. 路徑重構與結果輸出

    • 通過前驅指針重構完整路徑
    • 輸出最小成本、到達天數、時刻和完整路徑
    • 若不存在滿足約束的路徑,輸出 - 1

該算法通過時間擴展網絡模型成功處理了周期性宵禁約束,結合動態規劃跟蹤節點風險帶來的額外時間成本,在滿足所有防疫和通行約束的前提下找到了最小成本運輸方案。

擴展思考

本題可以從以下幾個方向進行擴展:

  1. 引入動態風險等級,節點風險隨時間變化,增加狀態動態性
  2. 考慮物資保質期,要求在特定時間前送達,增加時間約束復雜度
  3. 擴展為多批次運輸,考慮批次間的資源競爭和協調
  4. 加入隨機事件(如道路臨時封閉、風險等級突變),設計魯棒性方案

這些擴展更貼近疫情期間復雜多變的實際運輸場景,對算法的適應性和前瞻性提出了更高要求。

通過本題的求解可以看出,NOI 題目注重考察選手將實際問題抽象為算法模型的能力,尤其是處理多重約束和周期性條件的能力,這要求選手不僅掌握基礎算法,還要具備靈活的問題建模能力。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917229.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917229.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917229.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

加密與安全

目錄 一、URL編碼&#xff1a; 二、Base64編碼&#xff1a; 三、哈希算法&#xff1a; 四、Hmac算法&#xff1a; 五、對稱加密算法&#xff1a; 一、URL編碼&#xff1a; URL編碼是瀏覽器發送數據給服務器時使用的編碼&#xff0c;它通常附加在URL的參數部分。之所以需要…

EasyExcel 公式計算大全

EasyExcel 是基于 Apache POI 的封裝&#xff0c;主要專注于簡化 Excel 的讀寫操作&#xff0c;對于公式計算的支持相對有限。以下是 EasyExcel 中處理公式計算的全面指南&#xff1a;1. 基本公式寫入1.1 寫入簡單公式Data public class FormulaData {ExcelProperty("數值…

2025年AI+數模競賽培訓意見征集-最后一輪

在過去幾天的“AI時代下2025年數模競賽培訓課程需求調研緊急征集”我們收到了大量老師、學生的反饋。我們通過大家的實際需求&#xff0c;編寫了下述2025年AI時代下最新的數學建模競賽教學課程課程表&#xff0c;具體授課內容以及相關課件、支撐材料都將會免費發布&#xff0c;…

Qwen2 RotaryEmbedding 位置編碼僅僅是第一層有嗎

Qwen2 RotaryEmbedding 位置編碼僅僅是第一層有嗎,還是全部層都有 Qwen2 模型中的 Rotary Embedding(旋轉位置編碼)是應用于所有 Transformer 層 的,而非僅第一層。 1. Transformer 架構的核心邏輯 Qwen2 基于 Decoder-only Transformer 架構,而位置編碼(如 Rotary Emb…

CNN卷積神經網絡之LeNet和AlexNet經典網絡模型(三)

CNN卷積神經網絡之LeNet和AlexNet經典網絡模型&#xff08;三&#xff09; 文章目錄CNN卷積神經網絡之LeNet和AlexNet經典網絡模型&#xff08;三&#xff09;深度學習兩大經典 CNN 模型速覽1. LeNet-5&#xff1a;CNN 的開山之作&#xff08;1998&#xff09;2. AlexNet&#…

江協科技STM32 12-2 BKP備份寄存器RTC實時時鐘

這一節我們要講的主要內容是RTC實時時鐘&#xff0c;實時時鐘本質上是一個定時器&#xff0c;但是這個定時器是專門用來產生年月日時分秒&#xff0c;這種日期和時間信息的。所以學會了STM32的RTC就可以在STM32內部擁有一個獨立運行的鐘表。想要記錄或讀取日期和時間&#xff0…

【10】大恒相機SDK C++開發 ——對相機采集的原圖像數據IFrameData裁剪ROI 實時顯示在pictureBox中,3種方法實現(效率不同)

文章目錄1 在回調函數中實現2 獨立封裝調用2.1 獲取圖像寬、高、pBuffer、channel2.2 內存圖像數據截取ROI并顯示2.3 回調函數調用3 for循環嵌套 方法24 for循環嵌套 方法35 按行復制數據提高效率&#xff0c;但很耗內存6 unsafe代碼 解釋及注意事項 看我另一篇文章7 ConvertTo…

ubuntu22.04系統入門 linux入門(二) 簡單命令 多實踐以及相關文件管理命令

以下有免費的4090云主機提供ubuntu22.04系統的其他入門實踐操作 地址&#xff1a;星宇科技 | GPU服務器 高性能云主機 云服務器-登錄 相關兌換碼星宇社區---4090算力卡免費體驗、共享開發社區-CSDN博客 之所以推薦給大家使用&#xff0c;是因為上面的云主機目前是免費使用的…

分布式ID方案(標記)

一、參考文章-標記 分布式ID方案有哪些&#xff1f;雪花算法如何搞定時鐘回撥和動態機器ID&#xff1f; 二、應用 1.百度 uid-generator github項目地址 原理參考 2.百度 uid-generator 擴展應用 燈官網 燈 項目代碼 lamp-util 單元模塊 lamp-util 單元模塊子模塊 lamp-…

std::map 加鎖

在并發環境下使用std::map&#xff0c;必須采取同步措施。 在并發環境下對 std::map 進行不加鎖的讀寫操作會導致嚴重的線程安全問題&#xff0c;主要會產生以下幾種問題&#xff1a; ?? 主要風險與后果數據競爭&#xff08;Data Race&#xff09; 當多個線程同時修改同一個鍵…

學習筆記090——Ubuntu 中 UFW 防火墻的使用

文章目錄1、允許特定的端口訪問2、允許特定 IP 訪問某個端口3、允許某個范圍的端口4、查看 UFW 狀態5、重新加載 UFW6、啟用 UFW7、關閉 UFW1、允許特定的端口訪問 # 允許 TCP 端口&#xff08;例如 80&#xff09;&#xff1a; sudo ufw allow 80/tcp# 允許 UDP 端口&#xf…

移動端 WebView 內存泄漏與性能退化問題如何排查 實戰調試方法匯總

在混合 App 應用中&#xff0c;WebView 頁面常承載復雜業務邏輯與交互。隨著用戶使用時間增長&#xff0c;特別在切換多個頁面或反復打開界面后&#xff0c;常常會出現性能下降、頁面卡頓、甚至白屏崩潰等現象。這通常是因為頁面存在內存泄漏、事件監聽未解綁或垃圾回收阻塞導致…

JSON 對象在瀏覽器中順序與后端接口返回不一致的問題

一、問題描述 后端接口返回一個字典表的JSON對象&#xff0c;頁面展示排序與預期排序不一致。 在瀏覽器調試面板Response中看到接口原始響應字符串&#xff0c;是期望順序&#xff1a;在Preview中看到&#xff0c; key “22” 被提到最前&#xff0c;順序發生變化&#xff1a;頁…

Spring MVC數據傳遞全攻略

Spring MVC數據傳遞一、前端到后端的數據傳遞1. 使用 RequestParam 傳遞簡單參數2. 使用 PathVariable傳遞路徑參數3. 使用RequestBody傳遞 JSON 數據二、后端到前端的數據傳遞1. 使用Model或 ModelAndView傳遞數據到前端2. 使用HttpServletResponse直接寫回數據3.使用Response…

倉庫管理系統-12-前端之頭部區域Header基于嵌套路由訪問個人中心

文章目錄 1 個人中心 1.1 DateUtils.vue(子組件) 1.2 Home.vue(父組件) 1.3 router/index.js(嵌套路由) 1.4 index.vue(路由占位符) 2 Header.vue 2.1 頁面布局 2.2 toUser方法 2.3 初始加載 2.4 Header.vue 頭部區域Header中有一個個人中心下拉菜單,點擊個人中心選項,通過嵌…

【智能協同云圖庫】第七期:基于AI調用阿里云百煉大模型,實現AI圖片編輯功能

摘要&#xff1a;AI 高速發展賦能傳統業務&#xff0c;圖庫網站亦有諸多 AI 應用空間。以 AI 擴圖功?能為例&#xff0c;讓我們來學習如何在項目?中快速接入 AI 繪圖大模型。?用戶可以選擇一張已上傳的圖片&#xff0c;?通過 AI 擴圖得到新的圖片&#xff0c;希望可以幫到大…

Notepad++插件安裝

方式一&#xff1a;自動安裝&#xff08;有些notepad并不好用&#xff0c;推薦方式二&#xff09;工具欄-》插件-》插件管理如下點擊安裝后會提示&#xff0c;后端安裝&#xff0c;安裝成功后自動啟動&#xff0c;本人使用的v8.6.4的版本&#xff0c;插件基本都無法自動安裝&am…

git pull和git fetch的區別

git pull和git fetch是git版本控制系統中的兩個基本命令&#xff0c;它們都用于從遠程倉庫更新本地倉庫的信息&#xff0c;但執行的具體操作不同。git fetch:git fetch下載遠程倉庫最新的內容到你的本地倉庫&#xff0c;但它并不自動合并或修改你當前的工作。它取回了遠程倉庫的…

Item35:考慮virtual函數以外的其他選擇

在C++中,虛函數是實現多態的傳統方式,但并非唯一選擇。過度依賴虛函數可能導致派生類與基類的強耦合,或難以在運行時靈活切換行為。《Effective C++》Item35指出:應根據場景選擇更合適的替代方案,包括NVI模式、函數指針、策略模式等。本文解析這些方案的原理、適用場景及實…

Vue3 狀態管理新選擇:Pinia 從入門到實戰

一、什么是pinia? 在 Vue3 生態中&#xff0c;狀態管理一直是開發者關注的核心話題。隨著 Vuex 的逐步淡出&#xff0c;Pinia 作為官方推薦的狀態管理庫&#xff0c;憑借其簡潔的 API、強大的功能和對 Vue3 特性的完美適配&#xff0c;成為了新時代的不二之選。今天我們就來深…