1 条题解
-
0
课程与教材优化问题 - 算法详解
一、问题重述
有 门课程和 本教材,编号均为 到 :
-
通过课程 可获得 美元收益
-
目标是最大化净利润(收益 − 教材成本)
二、核心算法思想
2.1 环形数组展开 由于课程和教材编号是循环的(模 ),代码将数组展开为两倍长度:
for (int i = 1; i <= n; i++) { a[i + n] = a[i]; b[i + n] = b[i]; }这样将环形问题转化为线性问题,便于处理跨越边界的情况。
2.2 前缀和预处理
for (int i = 1; i <= 2 * n; i++) { sum[i] = sum[i - 1] + b[i]; }用于快速计算任意区间教材的总成本。
2.3 动态规划状态设计
定义两个数组:
- :考虑前 门课程,且必须选择课程 时的最大利润
- :前 门课程中 的最大值
三、算法执行过程
3.1 第一种情况:从课程 1 开始考虑
dp[1] = a[1] - boto(1, k); mx[1] = -1e18;- 选择课程 1,需要购买教材 1 到 ,计算初始利润
递推公式:
// 当 i+k-1 不超过 n 时 dp[i] = max(dp[i-1] - b[i+k-1], mx[i-1] - boto(i, i+k-1)) + a[i] // 当 i+k-1 超过 n 时(跨越边界) dp[i] = max(dp[i-1], mx[i-1] - boto(i, n)) + a[i]转移逻辑解释:
- :延续选择课程 ,只需额外购买教材
- :从之前的某个最优状态重新开始,购买完整区间教材
3.2 第二种情况:从课程 2 开始考虑
dp[1] = -1e18; // 不选择课程 1 mx[1] = 0; // 起始状态为 0这种处理确保我们可以从任意位置开始选择课程序列。
3.3 最终答案
for (int i = 1; i <= n; i++) { ans = max(ans, dp[i]); }取所有可能结束位置的最大值。
四、关键辅助函数
long long boto(int l, int r) { return sum[r] - sum[l - 1]; }快速计算教材区间 的总成本。
五、算法复杂度分析
5.1 时间复杂度
- 数组展开:
- 前缀和计算:
- 动态规划递推:
- 总时间复杂度:
5.2 空间复杂度
- 存储展开数组:
- 前缀和数组:
- DP 数组:
- 总空间复杂度:
六、算法正确性证明
6.1 环形处理正确性 通过将环形数组展开为两倍长度的线性数组,确保了所有可能的教材区间都能正确计算。
6.2 状态转移正确性 的定义确保:
- 必须选择课程
- 考虑了所有可能的前驱状态
- 正确处理了教材购买的成本计算
6.3 边界处理正确性
- 第一种情况:允许选择课程 1
- 第二种情况:不允许选择课程 1
- 两种情况的组合覆盖了所有可能的起始位置
七、算法特点
7.1 巧妙的状态设计 将问题分解为"必须选择当前课程"的子问题,简化了状态转移。
7.2 高效的转移计算 通过前缀和优化,将区间求和复杂度从 降为 。
7.3 完备的情况覆盖 通过两次 DP 运行,确保了所有可能的起始位置都被考虑。
7.4 线性复杂度 的复杂度适合处理大规模数据()。
八、样例验证
样例输入:
计算过程:
- 展开数组(略)
- 计算前缀和
- 执行 DP:
- 选择课程 0:利润
- 选择课程 1:利润
- 选择课程 2:利润
- 最大利润为 ,与样例输出一致
九、总结
该解法通过:
- 环形展开:将循环问题转化为线性问题
- 前缀和优化:快速计算区间成本
- 动态规划:系统性地寻找最优选择序列
- 两次运行:确保所有起始位置都被考虑
成功在 时间内解决了这个复杂的优化问题,体现了高效算法设计的思想。
-
- 1
信息
- ID
- 4165
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者