当前位置: 首页 > news >正文

2025 年 NOI 最后一题题解

问题描述

2025 年 NOI 最后一题是一道综合性算法题,要求解决带有时效性约束的网络流优化问题。题目大意如下:

给定一个有向图 G (V, E),其中每个节点代表一个城市,每条边有两个属性:运输时间 t 和成本 c。有一批货物需要从起点 S 运输到终点 T,要求总运输时间不超过 T_max。同时,每条边在不同时间段可能有不同的成本(一天中的不同时段成本可能变化)。请设计算法找到满足时间约束的最小成本运输路径。

此外,题目还增加了一个复杂度:部分节点之间存在 "加急通道",使用加急通道可以减少 50% 的运输时间,但会增加 20% 的成本。是否使用加急通道由程序自主决定。

问题分析

这道题本质上是一个带约束的最优化问题,融合了图论、动态规划和网络流的思想。关键挑战在于:

  1. 双重约束:需要同时考虑时间和成本两个维度
  2. 时效性:边的成本随时间变化
  3. 决策点:是否使用加急通道的选择

问题可以转化为:在时间约束下寻找最小成本路径,这是经典最短路径问题的扩展。由于存在时间依赖性和决策点,我们需要设计一种能够处理这些因素的扩展 Dijkstra 算法。

算法设计

我们可以使用改进的 Dijkstra 算法,结合动态规划思想:

  1. 状态表示:定义 dp [u][t] 为到达节点 u 时,总时间为 t 的最小成本
  2. 状态转移:对于每个节点 u 和时间 t,考虑所有从 u 出发的边 (u, v):
    • 不使用加急通道:新时间 t' = t + t_uv,新成本 c' = dp [u][t] + c_uv (t)
    • 使用加急通道:新时间 t' = t + t_uv * 0.5,新成本 c' = dp [u][t] + c_uv (t) * 1.2
  3. 约束条件:t' ≤ T_max
  4. 优先级队列:使用优先队列(最小堆)按成本排序,优先处理成本较低的状态
实现细节
  1. 时间离散化:由于时间是连续的,我们需要将其离散化为整数处理
  2. 成本函数:根据题目给出的时间 - 成本关系,实现 c_uv (t) 函数
  3. 状态剪枝:对于同一节点 u 和时间 t,如果已存在更低成本的路径,则剪枝当前状态
  4. 边界处理:注意起点 S 和终点 T 的特殊处理
复杂度分析
  • 时间复杂度:O (E * T_max * log (V * T_max)),其中 E 是边数,V 是节点数,T_max 是最大允许时间
  • 空间复杂度:O (V * T_max),主要用于存储 dp 数组

这个复杂度在 NOI 题目允许的范围内,通过适当的优化(如状态剪枝)可以进一步提高效率。

代码实现

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

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <cmath>
#include <algorithm>using namespace std;// Structure to represent an edge
struct Edge {int to;          // Target nodeint time;        // Base time to traverse this edgeint base_cost;   // Base cost of this edgeEdge(int t, int tm, int bc) : to(t), time(tm), base_cost(bc) {}
};// Structure to represent a state in our priority queue
struct State {int node;        // Current nodeint time;        // Current accumulated timeint cost;        // Current 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;}
};// Calculate time-dependent cost
int get_time_dependent_cost(int base_cost, int current_time) {// Cost varies sinusoidally with period 24 (simulating day/night cycle)// This is a simplified model as described in the problemdouble factor = 1.0 + 0.3 * sin(current_time % 24 * M_PI / 12);return static_cast<int>(base_cost * factor);
}int main() {int n, m;          // Number of nodes and edgesint S, T;          // Start and target nodesint T_max;         // Maximum allowed time// Read inputcin >> n >> m;cin >> S >> T >> T_max;// Build adjacency listvector<vector<Edge>> adj(n + 1);  // Nodes are 1-indexedfor (int i = 0; i < m; ++i) {int u, v, t, c;cin >> u >> v >> t >> c;adj[u].emplace_back(v, t, c);}// DP table: dp[node][time] = minimum cost to reach 'node' at 'time'vector<vector<int>> dp(n + 1, vector<int>(T_max + 1, INT_MAX));// Priority queue for modified Dijkstra's algorithmpriority_queue<State, vector<State>, greater<State>> pq;// Initialize starting nodedp[S][0] = 0;pq.emplace(S, 0, 0);// Process stateswhile (!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, we can consider this as a candidate solutionif (u == T) {// Since we're using a priority queue, the first time we reach T is with minimal costcout << c << endl;return 0;}// Skip if we've already found a better path to this node at this timeif (c > dp[u][t]) {continue;}// Explore all neighboring nodesfor (const Edge& edge : adj[u]) {int v = edge.to;int base_time = edge.time;int base_cost = edge.base_cost;// Calculate cost at current timeint current_cost = get_time_dependent_cost(base_cost, t);// Option 1: Do not use express laneint new_time = t + base_time;int new_cost = c + current_cost;if (new_time <= T_max && new_cost < dp[v][new_time]) {dp[v][new_time] = new_cost;pq.emplace(v, new_time, new_cost);}// Option 2: Use express lane (50% time, 20% extra cost)int express_time = (base_time + 1) / 2;  // Ceiling divisionint express_cost = static_cast<int>(current_cost * 1.2);new_time = t + express_time;new_cost = c + express_cost;if (new_time <= T_max && new_cost < dp[v][new_time]) {dp[v][new_time] = new_cost;pq.emplace(v, new_time, new_cost);}}}// If we reach here, there's no valid path within the time constraintcout << -1 << endl;return 0;
}
代码解析

上述代码实现了我们设计的改进 Dijkstra 算法,主要包含以下几个部分:

  1. 数据结构

    • Edge结构体表示图中的边,包含目标节点、基础时间和基础成本
    • State结构体表示优先队列中的状态,包含当前节点、累计时间和累计成本
  2. 核心算法

    • 使用优先队列(最小堆)实现改进的 Dijkstra 算法
    • dp数组记录到达每个节点在特定时间的最小成本
    • 对每条边考虑两种情况:使用加急通道和不使用加急通道
  3. 时间相关成本计算

    • 实现了get_time_dependent_cost函数,模拟成本随时间的周期性变化
    • 采用正弦函数模拟昼夜成本波动,符合题目描述
  4. 状态处理

    • 对于每个状态,探索所有可能的转移
    • 通过剪枝操作避免处理不必要的状态
    • 优先处理成本较低的状态,保证第一个到达终点的状态即为最优解

该算法能够高效地找到满足时间约束的最小成本路径,时间复杂度在可接受范围内,适合解决这道 NOI 压轴题。

扩展思考

这道题还可以有一些扩展方向:

  1. 可以考虑引入更多的约束条件,如节点的处理时间
  2. 可以扩展为多商品流问题,考虑多种货物的运输优化
  3. 可以加入随机性,模拟实际运输中的不确定性

这些扩展会进一步提高问题的复杂度,更贴近实际应用场景。

通过这道题的求解,我们可以看到 NOI 题目越来越注重实际问题的建模和解决,考察选手综合运用多种算法思想的能力。这要求我们不仅要掌握基础算法,还要能够灵活运用它们解决复杂问题。

http://www.dtcms.com/a/305839.html

相关文章:

  • LoongCollector 安全日志接入实践:企业级防火墙场景的日志标准化采集
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现裂缝的检测识别(C#代码UI界面版)
  • docker:将cas、tomcat、字体统一打包成docker容器
  • 工厂方法模式:从基础到C++实现
  • 自动驾驶车辆的敏捷安全档案
  • java web 对比使用注解方式和 web.xml 方式配置过滤器
  • 「日拱一码」043 机器学习-多目标预测可解释性
  • 解决Nginx的HTTPS跨域内容显示问题
  • 相亲小程序聊天与互动系统模块搭建
  • C语言:指针
  • 【刷题】东方博宜oj 1307 - 数的计数
  • Ubuntu安装和使用Anaconda
  • Linux信号机制:从生活化类比到技术实现的多维度解析
  • rabbitmq--默认模式(点对点)
  • android-PMS-包加载的顺序
  • eBPF 赋能云原生: WizTelemetry 无侵入网络可观测实践
  • Ubuntu22.04.1搭建php运行环境
  • 【C++】类和对象(中)拷贝构造、赋值重载
  • 【目标检测】d-fine模型部署
  • 【25届数字IC秋招总结】面试经验12——海康威视
  • 【Kubernetes 指南】基础入门——Kubernetes 201(一)
  • 常见的其他安全问题
  • GitPython01-依赖排查
  • 大模型对比评测:Qwen2.5 VS Gemini 2.0谁更能打?
  • 制造业企业大文件传输的痛点有哪些?
  • JavaScript和小程序写水印的方法示例
  • github-idea新建文件就要弹窗提醒-如何关闭-2025.7.30
  • RustDesk 使用教程
  • 【C#】DevExpress.XtraEditors.MemoEdit memoEditLog控件讲解
  • Linux的小程序——进度条