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

CCF-CSP 202212-2 训练计划:拓扑排序双图解法,15ms 100分代码详解

CCF-CSP 202212-2 训练计划:拓扑排序双图解法与高效实现策略

在CCF-CSP认证考试中,第二题往往考察考生对基础算法和数据结构灵活应用的能力。2022年12月的第二题"训练计划"就是一个典型的需要拓扑排序技巧的问题。本文将深入解析如何利用正反双图拓扑排序来计算任务的最早和最晚开始时间,并提供一份15ms内完成的高效C++实现代码。

1. 问题分析与建模

训练计划问题可以抽象为一个有向无环图(DAG)的任务调度模型。每个训练科目对应图中的一个节点,科目间的依赖关系构成图中的有向边。题目要求我们计算两项关键指标:

  1. 最早开始时间:每个任务在所有前置任务完成后能够开始的最早时间
  2. 最晚开始时间:在不影响总工期的前提下,每个任务可以开始的最晚时间

1.1 输入数据解析

输入包含三行关键信息:

  • 第一行:n(总天数)和m(科目数量)
  • 第二行:m个整数表示每个科目依赖的前置科目(0表示无依赖)
  • 第三行:m个正整数表示每个科目需要的训练天数
int n, m; cin >> n >> m; vector<int> p(m+1), t(m+1); for(int i=1; i<=m; i++) cin >> p[i]; for(int i=1; i<=m; i++) cin >> t[i];

1.2 图结构构建

我们需要构建两种图表示:

  1. 正向图:用于计算最早开始时间,边u→v表示v依赖于u
  2. 反向图:用于计算最晚开始时间,边v→u表示u被v依赖
vector<vector<int>> g(m+1), ginv(m+1); vector<int> in(m+1), out(m+1); for(int i=1; i<=m; i++) { if(!p[i]) continue; g[p[i]].push_back(i); // 正向图 in[i]++; // 入度统计 ginv[i].push_back(p[i]); // 反向图 out[p[i]]++; // 出度统计 }

2. 拓扑排序与时间计算

2.1 最早开始时间计算

最早开始时间采用正向拓扑排序,初始化所有无依赖任务的开始时间为1,然后按照拓扑序递推:

vector<int> mn(m+1); queue<int> q; for(int i=1; i<=m; i++) { if(!in[i]) { mn[i] = 1; // 无依赖任务第1天开始 q.push(i); } } int max_end = 0; while(!q.empty()) { int u = q.front(); q.pop(); for(int v : g[u]) { mn[v] = max(mn[v], mn[u] + t[u]); // 关键递推式 if(--in[v] == 0) q.push(v); } max_end = max(max_end, mn[u] + t[u] - 1); }

2.2 可行性检查与最晚时间计算

在计算最晚时间前,需要检查所有任务是否能在n天内完成:

if(max_end > n) { // 只输出最早时间 for(int i=1; i<=m; i++) cout << mn[i] << " \n"[i==m]; return; }

最晚开始时间采用反向拓扑排序,初始化所有不被依赖的任务的最晚开始时间为n - t[i] + 1

vector<int> mx(m+1, INF); for(int i=1; i<=m; i++) { if(!out[i]) { mx[i] = n - t[i] + 1; // 最后时刻开始 q.push(i); } } while(!q.empty()) { int u = q.front(); q.pop(); for(int v : ginv[u]) { mx[v] = min(mx[v], mx[u] - t[v]); // 关键递推式 if(--out[v] == 0) q.push(v); } }

3. 完整代码实现与优化

以下是整合了所有优化技巧的完整实现,包含输入输出优化和简洁的逻辑处理:

#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; void solve() { int n, m; cin >> n >> m; vector<int> p(m+1), t(m+1); for(int i=1; i<=m; i++) cin >> p[i]; for(int i=1; i<=m; i++) cin >> t[i]; // 建图及统计度数 vector<vector<int>> g(m+1), ginv(m+1); vector<int> in(m+1), out(m+1); for(int i=1; i<=m; i++) { if(!p[i]) continue; g[p[i]].push_back(i); in[i]++; ginv[i].push_back(p[i]); out[p[i]]++; } // 计算最早开始时间 vector<int> mn(m+1); queue<int> q; for(int i=1; i<=m; i++) { if(!in[i]) { mn[i] = 1; q.push(i); } } int max_end = 0; while(!q.empty()) { int u = q.front(); q.pop(); for(int v : g[u]) { mn[v] = max(mn[v], mn[u] + t[u]); if(--in[v] == 0) q.push(v); } max_end = max(max_end, mn[u] + t[u] - 1); } // 输出最早时间 for(int i=1; i<=m; i++) cout << mn[i] << " \n"[i==m]; // 检查可行性 if(max_end > n) return; // 计算最晚开始时间 vector<int> mx(m+1, INF); for(int i=1; i<=m; i++) { if(!out[i]) { mx[i] = n - t[i] + 1; q.push(i); } } while(!q.empty()) { int u = q.front(); q.pop(); for(int v : ginv[u]) { mx[v] = min(mx[v], mx[u] - t[v]); if(--out[v] == 0) q.push(v); } } // 输出最晚时间 for(int i=1; i<=m; i++) cout << mx[i] << " \n"[i==m]; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); solve(); return 0; }

4. 算法复杂度与优化分析

4.1 时间复杂度

该算法的时间复杂度主要由以下部分组成:

  1. 建图:O(m)
  2. 正向拓扑排序:O(m)
  3. 反向拓扑排序:O(m)

总时间复杂度为O(m),非常高效,可以轻松处理题目给出的m≤100的限制。

4.2 空间复杂度

空间消耗主要来自:

  1. 存储正向图和反向图:O(m)
  2. 存储入度和出度数组:O(m)
  3. 存储最早和最晚时间数组:O(m)

总空间复杂度也是O(m)

4.3 关键优化技巧

  1. 双图结构:同时维护正向图和反向图,避免重复计算依赖关系
  2. 拓扑排序队列复用:使用同一个队列进行正反两次拓扑排序
  3. 输入输出优化:使用ios::sync_with_stdio(false)加速IO
  4. 边界条件处理:及时检查总工期是否满足要求

5. 常见错误与调试技巧

在实现这类拓扑排序问题时,容易出现以下几种典型错误:

  1. 环状依赖检测:虽然题目保证依赖关系合法,但在实际应用中需要检测环
  2. 时间计算错误:注意开始时间和结束时间的转换(结束时间=开始时间+持续时间-1)
  3. 初始化遗漏:忘记初始化无依赖任务的最早开始时间为1
  4. 反向图构建错误:容易混淆边的方向

调试时可以:

  • 打印中间结果(如图结构、度数数组)
  • 对小样例手动计算验证
  • 检查边界条件(如所有任务都无依赖的情况)
// 调试打印图结构示例 void printGraph(const vector<vector<int>>& g) { for(int u=1; u<g.size(); u++) { cout << u << ": "; for(int v : g[u]) cout << v << " "; cout << endl; } }

6. CSP考试实战建议

针对CCF-CSP认证考试中的类似问题,建议采取以下策略:

  1. 快速建模:迅速将实际问题转化为图论模型
  2. 模板准备:提前准备好拓扑排序等常用算法的模板代码
  3. 分步验证:先确保部分正确性(如最早时间计算正确)
  4. 时间管理:第二题通常需要30-45分钟内完成
  5. 极端测试:考虑m=1、所有任务无依赖等边界情况

提示:在考试中,如果时间紧张,可以先确保最早时间的计算正确,这部分通常占50%的分数。最晚时间的计算可以作为加分项。

7. 扩展应用与变种思考

这种双图拓扑排序的方法不仅适用于训练计划问题,还可以解决许多类似的调度问题:

  1. 课程安排:计算课程的最早和最晚开课时间
  2. 项目计划:确定关键路径和任务浮动时间
  3. 依赖解析:软件包管理中的依赖关系处理
  4. 并行任务调度:确定任务的最优执行顺序

变种问题可能包括:

  • 带权重的依赖关系
  • 任务间的多种约束类型
  • 资源限制下的调度
  • 动态依赖关系变化

掌握这种双图拓扑排序的思想,能够帮助我们在面对复杂依赖关系时,高效计算出各项任务的时间窗口,为决策提供有力支持。

http://www.cnnetsun.cn/news/3320074.html

相关文章:

  • IntelliJ IDEA 2026.1 插件开发实战:3步构建首个Hello World插件并发布
  • Polars vs pandas 实战对比:新框架的语法能省多少代码
  • AIGC(生成式AI)试用 37 -- shell脚本(辅助生成)
  • NBTExplorer:3分钟掌握我的世界数据编辑,解锁游戏无限可能
  • Vue 3 + ECharts 5 数据大屏性能优化:100+图表渲染时间从 5s 降至 500ms
  • 终极实践指南:使用Evidently AI构建企业级ML/LLM数据质量监控系统
  • Visual C++ 6.0 与 2015-2022 运行库:2类安装场景与5个常见误区解析
  • FastAsyncWorldEdit 日志与回滚系统:如何监控和恢复玩家的编辑操作?
  • 面向对象方法 OOM 实战解析:从 5 大特征到 3 种复用设计模式
  • SAP IBP 2024 供应链规划实战:从业务顾问视角看 3 大核心场景配置
  • 7种测试用例设计方法实战:从QQ登录到公交卡充值的3个完整案例
  • Word 域代码全解析:F9与Ctrl+A的2种更新策略与5个应用场景
  • Word 2021/2024 多级列表与题注实战:3步解决‘表0-1’编号错误
  • 【AI Agent多智能体协作实战指南】:20年架构师亲授5大协同范式与3个避坑红线
  • 为什么92%的开发者部署DeepSeek失败?——本地运行DeepSeek的5个致命误区与3种稳定架构选型
  • 终极性能优化:NVIDIA gpt-oss-120b-Eagle3-v3的Eagle推测解码技术原理
  • 金属锈蚀、丝绸褶皱、液态玻璃…Midjourney质感生成失败?这5类材质必须规避的8个语义冲突陷阱,立即自查!
  • 为什么你总用不好ChatGPT?揭秘新手最常忽略的4层认知断层与即时生效的思维校准法
  • Loop:3分钟上手,让你的Mac窗口管理效率翻倍
  • 2026年蹦床公园设备建造厂家深度评测:资质与品质成选型分水岭
  • 仓储管理系统有哪些 从技术架构到集成能力的系统评估框架
  • CCF-CSP 202303-1 矩形面积交:3步核心算法与 O(n) 时间复杂度解析
  • Anaconda 2024.10.1 + PyCharm 2024.1 配置:3步完成虚拟环境创建与镜像源加速
  • 【技术深度】SoloPi录制回放在游戏测试领域的实战解码
  • 实战指南:5步掌握sam2_hiera_tiny.fb_r896图像特征提取的最佳实践
  • HomeAssistant 2024.12 远程访问方案对比:cpolar vs 反向代理 vs 第三方插件
  • STM32 低功耗模式:SLEEP / STOP / STANDBY 怎么选
  • HBase 2.1.1 单机模式部署:Ubuntu 20.04 下 3 步配置与常见报错解决
  • 微前端全局状态共享:跨子应用通信的四种方案对比
  • 文件上传后模型行为异常?ChatGPT文档处理链路全追踪,精准定位3类静默失效场景