1 条题解
-
0
题目理解
这是一个区间合并最优化问题,类似于石子合并,但有特殊约束:
- 初始状态:每个车厢独立为一组
- 合并条件:只能合并相邻的两组,且大小差 ≤ d
- 合并成本:
min((a*w + b*v) % 1001, (a*v + b*w) % 1001) - 目标:将所有 n 个车厢合并为一组的最小总成本
算法核心思想
1. 关键观察
注释中提到的
only O(d * log n) states are useful是解题关键:- 由于合并条件
|w - v| ≤ d,每个状态只能从有限的分割方式转移而来 - 状态数被限制在 O(d log n) 级别,而不是 O(n)
2. 记忆化搜索 (Memoization)
std::map<int64_t, uint64_t> record2;使用
map存储已计算的状态,避免重复计算。代码详细解析
1. 成本计算函数
int64_t calc(int64_t i, int64_t j) { i %= 1001; j %= 1001; return std::min((a * i + b * j) % 1001, (a * j + b * i) % 1001); }优化技巧:
- 对
i, j取模 1001,利用成本函数的周期性 - 取两种顺序的最小值,因为合并顺序可交换
2. 递归搜索函数
uint64_t dfs(int64_t x) { if (x <= 1) return 0; // 边界条件 auto iter = record2.find(x); if (iter != record2.end()) return iter->second; __int128 ans = (__int128)1 << 120; // 初始化为极大值 // ... }3. 核心的状态转移
for (int64_t i = x / 2, j = x - i; i > 0 && j - i <= d; i -= 1, j += 1) { ans = std::min(ans, (__int128)dfs(i) + (__int128)dfs(j) + calc(i, j)); }枚举策略分析:
- 从最平衡的分割
(x/2, x - x/2)开始 - 向两边扩展,直到大小差超过 d
- 最多枚举 d+1 种分割方式
为什么这样枚举有效:
例:x=10, d=2 枚举的分割: i=5, j=5 (差=0) i=4, j=6 (差=2) i=3, j=7 (差=4) → 停止算法复杂度分析
时间复杂度
- 状态数:O(d log n)
- 每个状态 x 产生约 O(d) 个子状态
- 递归深度为 O(log n)
- 总复杂度:O(d² log n) 或 O(d log² n)
空间复杂度
- O(d log n) 用于存储记忆化状态
数值处理技巧
1. 防止整数溢出
__int128 ans = (__int128)1 << 120;使用
__int128存储中间结果,避免在累加时溢出。2. 模运算优化
i %= 1001; j %= 1001;利用成本函数的周期性,减少大数运算。
学习要点
1. 记忆化搜索模式
if (记录中存在) return 记录值; 计算新值; 记录[状态] = 新值; return 新值;这是标准的记忆化搜索模板。
2. 状态空间剪枝
通过问题约束
|w-v|≤d大幅减少状态数,这是优化DP的关键。3. 枚举顺序优化
从中间向两边枚举,可以:
- 优先考虑更平衡的分割(通常更优)
- 及时终止无效枚举
4. 大数处理
在竞赛编程中,要注意:
- 使用足够大的数据类型
- 预防中间结果溢出
实例演示
以样例
n=5, d=1, a=1, b=1为例:f(5) = min{ f(2)+f(3)+calc(2,3), // 2+3分割 f(3)+f(2)+calc(3,2) // 同上(对称) }计算过程:
calc(2,3) = min((1*2+1*3)%1001, (1*3+1*2)%1001) = min(5,5)=5- 递归计算 f(2), f(3) 等子问题
优化思路
- 迭代DP:对于较小的 n,可以使用自底向上的DP
- 数学优化:分析成本函数的数学性质,寻找规律
- 状态压缩:利用问题特性进一步减少状态数
总结
这个解法展示了如何通过:
- 记忆化搜索 避免重复计算
- 问题约束 剪枝状态空间
- 数值优化 处理大数运算
- 智能枚举 提高搜索效率
来解决大规模的最优化问题,是竞赛中处理此类问题的经典方法。
- 1
信息
- ID
- 4561
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者