赞
踩
0-1背包问题
给定n个重量为w1/w2/w3.../wn
,价值为v1/v2/v3.../vn
的物品,容量为C
的背包,求这个背包可以装下的价值最高的子集,每个物品只能使用一次
w = {2,1,3,2} //重量
v = {12,10,20,15} // 价值
C = 5 // 容量
#最佳子集为 [2,1,2] 12+10+15 = 37
对每个物品,都有选择/不选
两个状态,这样解空间就可以描述成一个状态树,
可以很像求子集
的问题,不同的地方就是,子集的重量和需要小于背包重量,否则需要剪枝
,如2->1->3
不满足报容量,则3
不可成为2->1
这个子路径的叶节点,然后统计每一个子集的价值总和,比较得出最大值即可;
void backtrack(vector<int> w,vector<int> v,int C,int index,int& res,int val){ if(index == w.size() || C<=0){ res = max(res,val); return ; } if(C >= w[index]){ backtrack(w,v,C-w[index],index+1,res,val+v[index]); } backtrack(w,v,C,index+1,res,val); } int main() { vector<int> w = {2,1,3,2}; vector<int> v = {12,10,20,15}; int res = 0,C=5; backtrack(w,v,C,0,res,0); cout<<res<<endl; return 0; }
因为上述方法会存在很多重复子问题的计算,所以我们可以保存子问题计算的结果,从而达到剪枝的效果
int backtrack2(vector<int> w,vector<int> v,int C,int index,int val,vector<vector<int>>& memo){ if(index == w.size() || C <= 0){ return val; } if(memo[index][C]!=-1) return memo[index][C]; int aa = 0; if(C >= w[index]){ aa = backtrack2(w,v,C-w[index],index+1,val+v[index],memo); } aa = max(aa,backtrack2(w,v,C,index+1,val,memo)); memo[index][C] = aa; return aa; } int main() { vector<int> w = {2,1,3,2}; vector<int> v = {12,10,20,15}; int res = 0; vector<vector<int>> memo(w.size(),vector<int>(6,-1)); res = backtrack2(w,v,5,0,0,memo); cout<<res<<endl; return 0; }
我们可以使用一个二维数组dp[n][C+1]
,表示第几个物体 在C容量下的价值;
则dp[i][j] = max(dp[i-1][j] , dp[i-1][j-w[i] ] + v[i])
表示w[i]
第i
个物体不装入背包的话,则直接由上一层价值传递,如果w[i]
装入背包的话,则由dp[i-1][j-w[i]]
上一层 减去物体重量时的价值 + 物体本身的价值,取两者大,表格示意图如下:
| 背包容量
----------
物体id |
int dfs_dp(vector<int> w ,vector<int> v,int C){ vector<vector<int>> dp(w.size(),vector<int>(C+1,0)); for (int i = 0; i <= C; i++) { dp[0][i] = w[0] <= i ? v[0] : 0; } for(int i=1;i<w.size();i++){ for(int j=1;j <= C;j++){ dp[i][j] = dp[i-1][j]; if( j >= w[i] ){ dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]); } } } for(auto i:dp){ for(auto j:i) cout<<j<<" "; cout<<endl; } return dp[w.size()-1][C]; } int main() { vector<int> w = {2,1,3,2}; vector<int> v = {12,10,20,15}; int res = 0; res = dfs_dp(w,v,5); cout<<res<<endl; return 0; } /* 0 0 12 12 12 12 0 10 12 22 22 22 0 10 12 22 30 32 0 10 15 25 30 37 */
给定一个仅包含正整数的非空数组,确定该数组是否可以分成两部分,要求两部分的和相等
[1,2,3,6] --> [1,2,3],[6]
int dfs_dp(vector<int> w ,int C){ // 抽象为: 使用n个(重量w)的物体,能否正好装满C容量的背包; // dp[n][c] 表示 使用n个物体,是否可以装满 C容量的背包; int n = w.size(); vector<vector<bool>> dp(n,vector<bool>(C+1,false)); dp[0][w[0]] = 1; // 初始,使用w[0]物体,可以正好装满w[0]容量的背包 // dp[i][j] = dp[i-1][j]; 当前物体不入袋,继承上一个物体的状态 // dp[i][j] = dp[i][j] || dp[i-1][j-w[i]]; 当前物体入袋||不入袋,只要有一个状态可以装满 即为True for(int i=1;i<n;i++){ for(int j=0;j<=C;j++){ dp[i][j] = dp[i-1][j]; if(j>=w[i]) dp[i][j] = dp[i][j] || dp[i-1][j-w[i]]; } } for(auto i:dp){ for(auto j:i) cout<<j<<" "; cout<<endl; } return dp[n-1][C]; } int main() { vector<int> v1 = {1,2,3,6}; bool flag = dfs_dp(v1,6); cout<<flag<<endl; return 0; } /* 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 1 0 1 1 0 1 0 1 0 1 1 0 1 */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。