干碎背包问题

背包问题模板题

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];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值