#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]。
阅读全文
相关推荐













