背包问题模板题
1 01背包入门 捡骨头
#include<bits/stdc++.h>
using namespace std;
int w[1001];
int v[1001];
int dp[1001][1001];
int main(){
int T;
int N,V;
cin>>T;
while(T--){
cin>>N>>V;
memset(dp,0,sizeof(dp));
for(int i=1;i<=N;i++)
cin>>w[i];
for(int i=1;i<=N;i++)
cin>>v[i];
for(int i=1;i<=N;i++)
{
for(int vol=0;vol<=V;vol++)//hdu题目有体积为0的数据
if(vol<v[i])dp[i][vol]=dp[i-1][vol];//这句话不能省略否则楼下数组越界
else
dp[i][vol]=max(dp[i-1][vol],dp[i-1][vol-v[i]]+w[i]);
}
cout<<dp[N][V]<<endl;
}
system("pause");
return 0;
}
2 01背包入门 分割等和子集
思路: 转化成target为 sum/2 的01背包问题,这里用的是一维数组的写法,注意是从sum遍历到0,因为需要用到上一次的结果。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
// int dp[201][20001] = {0};
int dp[20001] = {0};
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2) return false;
sum /= 2;
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = sum; j >= nums[i]; j--) {
dp[j] = max(dp[j], dp[j-nums[i]]);
}
}
return dp[sum];
}
};
3 01背包 目标和
思路: 令正数和为x,负数和的绝对值为y,则有 x - y = target,x + y = sum
然后就可以转化成 x = (target + sum) / 2 的01背包问题,有一点要注意的是当 target + sum 为奇数时无解,因为 x = (target + sum) / 2成立的前提条件是target这个值能被构造出来,只要target能被构造出来,target + sum 就一定为偶数2x
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int dp[1001] = {0};
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (abs(target) > sum || (sum + target) % 2 == 1) return 0;
// target不达标
int target1 = (target + sum) / 2;
// x + y = sum, x - y = target => x =
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = target1; j >= nums[i]; j--) {
dp[j] += dp[j-nums[i]];
}
}
return dp[target1];
}
};
4 完全背包入门
思路: 朴素三重循环解法,通过dp之间的关系=>二重循环=>内存优化
#include<bits/stdc++.h>
using namespace std;
int N,V;
int vol[1001];
int val[1001];
int dp[1001][1001] ;
int f[1001];
int main(){
cin >> N >> V;
for (int i = 1; i <= N; i++) {
cin >> vol[i] >> val[i];
}
// for (int i = 1; i <= N; i++) {
// for (int j = 0; j <= V; j++) {
// // for (int k =0; k * vol[i] <= j; k++) {
// // dp[i][j] = max(dp[i][j], dp[i-1][j-k*vol[i]] + k*val[i]);
// // }
// // 朴素解法超时
// //利用dp[i][j]与dp[i][j-v]的关系
// // dp[i][j]= max(dp[i-1][j],dp[i-1][j-v]+v,dp[i-1][j-2v]+2v....)
// // dp[i][j-v] = max(dp[i-1][j-v],dp[i-1][j-2v]+v);
// if (j < vol[i]) dp[i][j] = dp[i-1][j];
// else dp[i][j] = max(dp[i-1][j], dp[i][j-vol[i]] + val[i]);
// }
// }
// 以上是二维数组写法
for (int i = 1; i <= N; i++) {
for (int j = vol[i]; j <= V; j++) {
f[j] = max(f[j], f[j-vol[i]] + val[i]);
}
}
cout << f[V] << endl;
system("pause");
return 0;
}
5. 完全背包 单词拆分
法一 记忆化搜索:
class Solution {
public:
set<string> S;
map<int,int> M;
bool wordBreak(string s, vector<string>& wordDict) {
for (auto str : wordDict) S.insert(str);
return dfs(0, s);
}
bool dfs (int start, string s) {
if (M[start] == 1) return false;
if (start == s.size()) return true;
for (int i = start; i < s.size(); i++) {
string str = s.substr(start, i-start+1);
if (S.find(str) != S.end() && dfs(i+1, s)) {
return true;
}
}
M[start] = 1;
return false;
}
};
法二 动态规划:dp[i] = 1 代表能被分解,所以dp[i] = dp[j] +s.substr(j,i-j+1)存在,没看出和完全背包的关系
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string> S = set<string>(wordDict.begin(), wordDict.end());
vector<int> dp(s.size()+1);
dp[0] = 1;
for (int i = 0; i < s.size(); i++) {
for (int j = 0; j <= i; j++) {
if (S.find(s.substr(j, i-j+1)) != S.end() && dp[j] == 1) {
dp[i+1] = 1;
break;
}
}
}
return dp[s.size()];
}
};
6 完全背包 完全平方数
分析 :首先由于是完全背包,所以套完全背包的格式。即背包从小到大。其次要注意的是dp[j]代表和为j的最少数量,所以一开要初始化为INF,用0来迭代。目前还不是完全理解
class Solution {
public:
int numSquares(int n) {
// dp[j]代表,和为j的最少数量
int dp[10001];
fill(dp,dp+10001,999999);
dp[0] = 0;
for (int i = 1; i <= sqrt(n); i++) {
for (int j = 1; j <= n; j++) {
if(j >= i * i) dp[j] = min(dp[j], dp[j-i*i] + 1);
}
}
return dp[n];
}
};
7.多重背包入门
方法1:枚举k个的时候限制个数就行,但是三重循环会超时
方法2:将每一种物品的s种取法通过1,2,4,8··· 映射到数组上,经过验证,映射后的数组能过取到0~s种该物品,所以可行,最后用01背包收尾。
#include<bits/stdc++.h>
using namespace std;
int N,V;
int vol[22000];
int val[22000];
int dp[1001][1001] ;
int f[2001];
int main(){
cin >> N >> V;
int v, w, s;
int cnt = 0;
for (int i = 1; i <= N; i++) {
cin >> v >> w >> s;
int k = 1;
while (k <= s) {
cnt ++;
vol[cnt] = k * v;
val[cnt] = k * w;
s -= k;
k *= 2;
}
if (s > 0) {
cnt ++;
vol[cnt] = s * v;
val[cnt] = s * w;
}
}
for (int i = 1; i <= cnt; i++) {
for (int j = V; j >= vol[i]; j--) {
f[j] = max(f[j], f[j-vol[i]] + val[i]);
}
}
cout << f[V] << endl;
system("pause");
return 0;
}
8 分组背包
每一个组只能取一件物品,用二维数组来存储。其实感觉和01背包非常类似,递推方程几乎是一样的,仅仅是多了一个分组
#include<bits/stdc++.h>
using namespace std;
int N,V;
int group[101];
int vol[101][101];
int val[101][101];
int dp[1001][1001];
int f[2001];
int main(){
cin >> N >> V;
int cnt = 0;
int v, w;
for (int i = 1; i <= N; i++) {
cin >> group[i];
for (int j =1; j <= group[i]; j++) {
cin >> vol[i][j] >> val[i][j];
}
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= 0; j--) {
for (int k = 1; k <= group[i]; k++) {
if(j >= vol[i][k]) f[j] = max(f[j], f[j-vol[i][k]] + val[i][k]);
}
}
}
cout << f[V] <<endl;
system("pause");
return 0;
}
背包问题进阶
经过验证,如果物品放内循环,背包放外循环,这样可以算排列数。
1 完全背包求排列
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
int dp[1001] = {0};
dp[0] = 1;
for (int j = 0; j <= target; j++) {
for (int num : nums) {
if (j >= num && dp[j] <= INT_MAX - dp[j - num]) dp[j] += dp[j - num];
}
}
return dp[target];
}
};