背包九讲整理

背包九讲类型汇总:

1.01背包问题

2.完全背包问题

3.多重背包问题

4.混合背包问题

5.二维费用的背包问题

6.分组背包问题

7.有依赖的背包问题

8.背包问题求方案数

9.求背包问题的具体方案

1. 01背包问题 Acwing 02

有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。

第 i件物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

朴素版解法:二维空间解法
每件物品只能选一次,对于每种物品,我们有两种选择

1.不选 -> dp[i][j]=dp[i-1][j]
等于选前i-1个物品,空间为j情况下的最优解
2.选 -> dp[i][j]=dp[i-1][j-v[i]]+w[i]
如果选的话,前i-1个物品的体积最多为j-v[i]

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int n, V;
    cin >> n >> V;
    int v[n+1],w[n+1];
    for(int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];
    int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
    memset(dp,0,sizeof(dp));
    for(int i = 1;i <=n; i++)
        for(int j = 1; j <= V; j++)
            if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            else dp[i][j] = dp[i-1][j];//这句容易忘
    cout <<dp[n][V];
}

解法二:滚动数组优化:(实际上只需要一个数组)

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int n, V;
    cin >> n >> V;
    int v[n+1],w[n+1];
    for(int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];
    int dp[V+1];////dp[j]表示背包容量是j的情况下的最大价值。
    memset(dp,0,sizeof(dp));
    for(int i = 1;i <=n; i++)
        for(int j = V;j>= v[i];j--)
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    cout <<dp[V];
}

注:若题目要求装满背包,即将物品恰装入一个容量为m的背包中,只需要将初始化条件改一改即可,----将dp数组初始化为负无穷,dp[0]=0,即可确保状态一定是从0转移过来的。

2.完全背包问题 Acwing 03

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

朴素版解法:二维空间解法
也是两种选择,选或不选,只不过每个物品可以选无限次,在01的基础上把
dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i])
改为
dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]) (dp[i][j-v[i]] 可能已经是选过第i个的了)即可

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int n, V;
    cin >> n >> V;
    int v[n+1],w[n+1];
    for(int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];
    int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
    memset(dp,0,sizeof(dp));
    for(int i = 1;i <=n; i++)
        for(int j = 1; j <= V; j++)
            if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i][j-v[i]]+w[i]); //注意
            else dp[i][j] = dp[i-1][j];
    cout <<dp[n][V];
}

优化空间版解法:
转移方程为dp[j]=max(dp[j],dp[j-v[i]]+w[i])

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int n,m;
    cin >> n >>m;
    int v[n+1],w[n+1];
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    int dp[m+1];
    memset(dp,0,sizeof(dp));
    for(int i = 1; i <= n; i++)
        for(int j = v[i]; j <= m; j++)//注意
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    cout << dp[m];
}

3.多重背包问题

方法1:o(n^3)做法 Acwing 04

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

数据范围

0<N,V1000<N,V≤100
0<vi,wi,si100

思路分析

多重背包是选0个,1个,2个…s[i]个
即dp[j]=max(dp[j],dp[j - v[i] * k]+w[i] * k)
k=1,2,3,…s[i]
那么再加一层循环表示选多少个就可以了

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int v[104],s[104],w[104];
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <=n;i++)
        cin >> v[i] >> w[i] >> s[i];
    int dp[104];
    memset(dp,0,sizeof(dp));
    for(int i=1; i <= n; i++)
        for(int j = m; j >= v[i];j--){
            for(int k = 1; j-k*v[i]>= 0 && k <= s[i]; k++)//注意条件
                dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]);
        }
    cout << dp[m];
}

方法2:二进制优化做法 Acwing 05

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

数据范围:

0<N10000<N≤1000
0<V20000<V≤2000
0<vi,wi,si2000

分析:O(n^3)的方法肯定会超时,需要(N^2*logN)的方法;

把si看做是一个二进制数,把si个可以拆成logsi个物体,这些物体肯定可以组成si个物体,转化成01背包问题;

#include<iostream>
#include<vector>
using namespace std;
#define N 2004
int v[N],w[N],s[N];
int main(){
    vector<int> vv = {0};
    vector<int> ww = {0};
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> v[i] >> w[i] >>s[i];
    //二进制拆分
    for(int i = 1; i<= n; i++){
        for(int k = 1; k <= s[i];k*=2){
            vv.push_back(k*v[i]);
            ww.push_back(k*w[i]);
            s[i] -= k;
        }
        if(s[i]) {
            vv.push_back(s[i]*v[i]);
            ww.push_back(s[i]*w[i]);
        }
    }
    //新物品的个数
    n = vv.size()-1;
    //常规01背包问题
    vector<int> dp(m+1);
    for(int i = 1; i<= n; i++){
        for(int j = m; j >= vv[i];j--){
            dp[j] = max(dp[j],dp[j-vv[i]]+ww[i]);
        }
    }
    cout << dp[m];
    
}

题目3:多重背包终极版… Acwing 06

题目跟上面一样,但是数据范围如下
在这里插入图片描述

4.混合背包问题 Acwing 07

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

#include<iostream>
#include<vector>
using namespace std;
#define N 1004
int main(){
    vector<int> v(N),w(N),s(N);
    int m,n;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> v[i] >> w[i] >> s[i];
        if(s[i] == -1) s[i] = 1;//01背包相当于只能选一次的多重背包
    }
    vector<int> dp(N);
    for(int i = 1; i <= n; i++){
        if(s[i] == 0){//可以选无数次
            for(int j = v[i];j <= m; j++)
                dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        } 
        else if(s[i] > 0){//多重背包问题,二进制优化,优化时直接计算
            for(int k = 1; k <= s[i]; k*=2){
                s[i] -= k;
                for(int j = m; j >= k*v[i];j--)
                    dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]);
            }
        }
        //多重背包最后一个,或者01背包
        for(int j = m; j >= s[i]*v[i];j--)
            dp[j] = max(dp[j],dp[j-s[i]*v[i]]+s[i]*w[i]);
    }
    cout << dp[m];
    
}

猜你喜欢

转载自www.cnblogs.com/Aliencxl/p/12317725.html