活动介绍

#include<bits/stdc++.h> #define ll long long using namespace std; bool f[7][1010]; int a[7]={0,1,2,3,5,10,20},v[1010],cnt,s[7]; int main(){ ios::sync_with_stdio(0);cin.tie(0); for(int i=1;i<=6;i++){ cin>>s[i]; } f[0][0]=1;//前0件物品放入容量为0的背包 for(int i=1;i<=6;i++){ for(int j=0;j<=1000;j++){ for(int k=1;k<=s[i];k++){ f[i][j]=f[i-1][j]; if(j>=k*a[i])f[i][j]|=f[i-1][j-k*a[i]]; } } } cnt=0; for(int i=1;i<=1000;i++) if(f[6][i])cnt++; cout<<"Total="<<cnt; return 0; }

时间: 2025-07-23 20:05:36 浏览: 3
<think>我们正在讨论动态规划解决背包问题,特别是0-1背包问题。用户要求分析C++代码并讨论优化方法。根据引用内容,我们有一个优化版的代码,它使用了一维数组(滚动数组)来降低空间复杂度。我们将分析该代码,并解释优化原理。 ### 0-1背包问题动态规划解法 0-1背包问题描述:给定$n$个物品,每个物品有重量$w_i$和价值$v_i$,以及一个容量为$m$的背包。如何选择物品装入背包,使得背包中物品的总价值最大?每个物品要么选要么不选。 #### 基本动态规划(二维数组) 使用二维数组`dp[i][j]`表示考虑前$i$个物品,在背包容量为$j$时能获得的最大价值。 状态转移方程: $$ dp[i][j] = \begin{cases} dp[i-1][j] & \text{若 } j < w_i \\ \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) & \text{若 } j \geq w_i \end{cases} $$ #### 优化:一维数组(滚动数组) 我们可以观察到,状态`dp[i][j]`只依赖于`dp[i-1][j]`和`dp[i-1][j-w_i]`。因此,我们可以使用一维数组`dp[j]`来存储当前层的状态,并覆盖上一层状态。但要注意:更新顺序必须从大到小(背包容量从大到小)更新,以避免重复放入同一物品(0-1背包要求每个物品只能选一次)。 为什么需要从大到小更新? - 如果从小到大更新,当我们更新`dp[j]`时,`dp[j-w_i]`可能已经被更新(即已经考虑了当前物品),这样就会导致同一物品被放入多次(变成了完全背包问题)。而从大到小更新,则保证在更新`dp[j]`时,`dp[j-w_i]`还是上一层的状态(即未考虑当前物品),从而满足0-1背包的要求。 优化后的状态转移方程(一维数组): $$ dp[j] = \max(dp[j], dp[j - w_i] + v_i) \quad \text{其中} j \text{从} m \text{递减到} w_i $$ #### 代码分析(引用中的优化代码) ```cpp #include<bits/stdc++.h> #include<math.h> using namespace std; int main(){ vector<int> w,v; vector<int> dp2; int m,n; cout<<"请输入背包的总重量"<<endl; cin>>m; cout<<"请输入物品数量"<<endl; cin>>n; vector<vector<int>> dp(n+1,vector<int>(m+1)); // 这里定义了一个二维数组,但后面并没有使用 w.push_back(0); v.push_back(0); for(int i=0 ; i<n ; i++){ cout<<"请输入物品"<<i+1<<"的重量和价值"<<endl; int new_w,new_v; cin>>new_w>>new_v; w.push_back(new_w); v.push_back(new_v); } // 一维数组背包问题 // 注意:这里dp2还没有初始化大小,直接使用dp2[j]会出错,需要先初始化大小为m+1,并初始化为0 // 添加初始化代码(原引用代码中缺失): dp2.resize(m+1, 0); // 初始化大小为m+1,所有元素为0 for(int i = 1 ; i<=n ; i++){ // 注意循环条件:j从m递减到w[i],且要求w[i]<=j,所以j>=w[i] for(int j=m ; j>=w[i] ; j--){ // 从大到小遍历 dp2[j] = max(dp2[j], dp2[j-w[i]]+v[i]); } } cout<<dp2[m]<<endl; return 0; } ``` #### 代码问题与优化 1. **初始化问题**:原引用代码中`dp2`没有初始化大小,直接使用`dp2[j]`会导致运行时错误。需要先调用`dp2.resize(m+1, 0)`。 2. **未使用的二维数组**:代码中定义了一个二维数组`dp`,但后续没有使用。可以删除。 3. **输入输出优化**:在实际应用中,可以简化输入输出,但作为示例保留。 4. **循环边界**:内层循环条件`j>=w[i]`,这样可以避免判断`j-w[i]`为负数的情况。 5. **空间优化**:使用一维数组将空间复杂度从$O(n \times m)$降低到$O(m)$。 #### 进一步优化 1. **常数优化**:循环中,内层循环可以从`m`到`w[i]`,但也可以提前跳出:当剩余容量小于最小物品重量时,可以提前结束循环(但需要记录最小重量,这里不展开)。 2. **数组复用**:我们只使用了一个一维数组,这是最优的空间优化。 3. **初始化优化**:初始化为0即可,因为没有任何物品时价值为0。 4. **输入优化**:可以边输入边处理,不必存储所有物品的重量和价值(但需要按顺序处理,且需要多次使用,所以存储是必要的)。 ### 总结 优化后的0-1背包问题动态规划代码使用一维数组,通过逆序更新背包容量来避免重复选择同一物品。空间复杂度为$O(m)$,时间复杂度为$O(n \times m)$。 §§相关问题§§ 1. 为什么0-1背包问题一维数组解法需要从大到小遍历背包容量? 2. 完全背包问题的一维数组解法与0-1背包有何不同? 3. 如何进一步优化0-1背包问题的时间复杂度?</think>### 动态规划解决0-1背包问题的C++代码分析与优化 #### 基础代码分析(二维数组法) ```cpp // 二维DP解法(空间复杂度O(n*m)) vector<vector<int>> dp(n+1, vector<int>(m+1, 0)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (j < w[i]) dp[i][j] = dp[i-1][j]; else dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]); } } cout << dp[n][m] << endl; ``` - **状态定义**:`dp[i][j]`表示考虑前$i$个物品,在容量$j$下的最大价值 - **转移方程**: $$ dp[i][j] = \begin{cases} dp[i-1][j] & j < w_i \\ \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) & j \geq w_i \end{cases} $$ - **缺点**:空间复杂度$O(n \times m)$,当背包容量大时内存消耗高 #### 优化方案1:一维数组(滚动数组) ```cpp // 一维DP优化(空间复杂度O(m)) vector<int> dp2(m+1, 0); for (int i = 1; i <= n; i++) { for (int j = m; j >= w[i]; j--) { // 关键:逆序遍历 dp2[j] = max(dp2[j], dp2[j - w[i]] + v[i]); } } cout << dp2[m] << endl; ``` - **优化原理**: 1. 利用滚动数组覆盖旧状态,空间复杂度降至$O(m)$ 2. **逆序更新**确保状态转移时使用的是$i-1$层的数据(正序会导致物品重复放入) - **数学证明**: 设当前状态$dp_j^{(i)}$,逆序更新保证: $$ dp_j^{(i)} = \max \left( dp_j^{(i-1)}, dp_{j-w_i}^{(i-1)} + v_i \right) $$ 而正序更新会导致$dp_{j-w_i}^{(i)}$已被覆盖,违反0-1背包约束 #### 优化方案2:循环边界剪枝 ```cpp for (int i = 1; i <= n; i++) { int min_j = max(w[i], 0); // 计算有效边界 for (int j = m; j >= min_j; j--) { ... } } ``` - 跳过$j < w_i$的无意义计算 - 时间复杂度常数级优化 #### 优化方案3:物品预处理 ```cpp // 按价值密度排序(贪心预处理) vector<pair<double, int>> density; for (int i = 1; i <= n; i++) density.push_back({(double)v[i]/w[i], i}); sort(density.rbegin(), density.rend()); // 按密度从高到低DP for (auto& [d, idx] : density) { for (int j = m; j >= w[idx]; j--) { dp2[j] = max(dp2[j], dp2[j - w[idx]] + v[idx]); } } ``` - **优势**:提前处理高价值物品,可能触发更多剪枝 - **注意**:不影响最优解正确性(DP本身无顺序依赖) #### 优化方案4:二进制拆分(多重背包) ```cpp // 适用于多重背包问题 vector<pair<int, int>> items; // (weight, value) for (int i = 1; i <= n; i++) { int cnt = k[i]; // 物品i的数量 for (int s = 1; s <= cnt; s *= 2) { items.push_back({s * w[i], s * v[i]}); cnt -= s; } if (cnt) items.push_back({cnt * w[i], cnt * v[i]}); } // 对items做0-1背包DP ``` - 将$k$个相同物品拆分为$O(\log k)$个物品 - 时间复杂度从$O(n \times m \times k)$降至$O(n \times m \times \log k)$ ### 性能对比 | 优化方案 | 空间复杂度 | 时间复杂度 | 适用场景 | |-------------------|------------|------------------|------------------| | 基础二维DP | $O(nm)$ | $O(nm)$ | 小规模数据 | | 一维滚动数组 | $O(m)$ | $O(nm)$ | 通用推荐方案 | | 循环边界剪枝 | $O(m)$ | $O(nm)$(常数优化)| 物品重量差异大 | | 物品预排序 | $O(m)$ | $O(nm + n\log n)$| 价值密度差异大 | | 二进制拆分 | $O(m)$ | $O(m \sum \log k)$| 多重背包问题 | > **关键结论**:一维逆序DP是0-1背包的最优通用解法,在保证$O(nm)$时间复杂度的同时将空间优化至$O(m)$,且能正确处理物品依赖关系[^1]。
阅读全文

相关推荐