公司动态

动态规划选数问题解析:从洛谷P15800到背包问题优化

📅 2026/8/10 6:23:04
动态规划选数问题解析:从洛谷P15800到背包问题优化
1. 项目概述洛谷P15800动态规划题目解析这道来自洛谷平台的P15800题目是GESP202603六级认证考试中的一道经典动态规划问题。题目要求从给定数组中选取若干个数使其满足特定条件如和等于目标值、数量限制等。这类选数问题在实际编程竞赛和算法面试中出现频率极高是检验考生动态规划掌握程度的试金石。我在刷题过程中发现许多初学者面对这类题目时容易陷入暴力搜索的思维定式。实际上通过合理的状态设计和转移方程优化这类问题的时间复杂度可以从指数级降到多项式级别。以本题为例合理运用动态规划可以将时间复杂度从O(2^n)优化到O(n*sum)其中n为数字个数sum为目标和。2. 动态规划解题思路拆解2.1 问题建模与状态定义首先需要明确题目要求的具体条件。典型的选数问题可能要求选取数字的和恰好等于目标值选取数字的数量不超过/恰好等于k个数字可以重复选取或不可重复选取以基础版本为例假设题目要求从数组nums中选取若干数使它们的和恰好等于target。我们可以定义dp[i][j]表示考虑前i个数时能否凑出和j。这种二维状态定义是解决背包类问题的通用方法。注意在实际编码时为了优化空间复杂度通常会使用滚动数组技巧将二维dp压缩为一维。但在初学阶段建议先写出完整的二维状态转移方程确保理解正确后再进行空间优化。2.2 状态转移方程推导对于每个数字nums[i]我们有两种选择不选这个数dp[i][j] dp[i-1][j]选这个数如果j nums[i]dp[i][j] dp[i-1][j-nums[i]]最终的转移方程为 dp[i][j] dp[i-1][j] || (j nums[i] ? dp[i-1][j-nums[i]] : false)初始化条件 dp[0][0] true 前0个数凑出和0是可行的 dp[0][j] false for j 0 前0个数无法凑出任何正数和2.3 空间优化技巧观察到dp[i]只依赖于dp[i-1]可以使用一维数组滚动更新vectorbool dp(target1, false); dp[0] true; for(int num : nums){ for(int j target; j num; j--){ dp[j] dp[j] || dp[j - num]; } }这里内层循环需要倒序遍历避免同一个数字被重复使用如果是完全背包问题即数字可重复使用则需要正序遍历。3. 完整代码实现与解析3.1 C标准解法#include iostream #include vector using namespace std; bool canSum(vectorint nums, int target) { vectorbool dp(target 1, false); dp[0] true; for (int num : nums) { for (int j target; j num; j--) { dp[j] dp[j] || dp[j - num]; } } return dp[target]; } int main() { int n, target; cin n target; vectorint nums(n); for (int i 0; i n; i) { cin nums[i]; } cout (canSum(nums, target) ? YES : NO) endl; return 0; }3.2 代码关键点解析dp数组初始化大小为target1因为需要考虑和为0到target的所有情况外层循环遍历每个数字逐步考虑是否选择该数字内层循环从target倒序检查到当前数字值避免重复使用状态转移dp[j] dp[j] || dp[j-num] 表示当前和j可以通过不选或选当前数字达到3.3 复杂度分析时间复杂度O(n*target)其中n为数字个数空间复杂度O(target)使用了一维dp数组4. 变种问题与扩展思考4.1 计算方案总数如果题目要求计算达到目标和的方案数只需修改状态转移方程dp[j] dp[j - num];初始化时dp[0]1其余为0。4.2 限制选取数字个数增加一维状态表示已选数字个数dp[i][k][j] // 前i个数选k个凑出和j转移方程相应扩展空间复杂度变为O(k*target)。4.3 输出具体方案需要额外记录路径信息通常有两种方法使用二维数组记录每个状态的前驱在dp完成后逆向回溯找出所选数字5. 常见错误与调试技巧5.1 初始化错误错误示例忘记初始化dp[0]true现象所有结果都为false检查打印dp数组初始状态5.2 循环顺序错误错误示例内层循环正序遍历现象数字被重复计算完全背包效果修正严格倒序遍历01背包或正序遍历完全背包5.3 边界条件处理数字含负数需要偏移处理将可能的负和映射到正索引大target值可能超出内存限制需要考虑剪枝或其他算法6. 洛谷平台提交注意事项输入输出格式严格匹配题目要求包括换行符等细节数据范围预先计算所需内存避免MLE内存超出限制特殊测试用例空数组target为0所有数字都大于target时间复杂度估算对于n100target1e4的情况O(n*target)1e6在C中完全可接受7. 动态规划学习建议从背包问题入手01背包、完全背包、多重背包是动态规划的经典模型画状态转移表对于二维dp问题手工填写小规模例子的dp表有助于理解分步调试在IDE中单步执行观察dp数组的变化过程对比记忆化搜索递归记忆化的实现方式有时更直观有助于理解状态定义我在最初学习动态规划时曾花费整整一周时间专门练习各种背包问题变种。建议初学者至少完成以下题目序列洛谷P1048 采药基础01背包洛谷P1616 疯狂的采药完全背包洛谷P1064 金明的预算方案依赖背包本题P15800综合应用动态规划的精髓在于状态定义和无后效性。一旦设计出正确的状态表示问题就解决了一大半。在实际比赛中我通常会先在草稿纸上明确写出dp数组的含义、转移方程、初始条件和最终答案的位置确认无误后再开始编码。