背包九讲整理

背包九讲类型汇总:

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
#include
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
#include
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
#include
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
#include
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,V10000<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
#include
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<N100000<V200000<vi,wi,si2000

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

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

#include
#include
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

题目跟上面一样,但是数据范围如下
背包九讲整理_第1张图片

4.混合背包问题 Acwing 07

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

物品一共有三类:

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

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

#include
#include
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];
    
}

 

5.二维费用的背包问题 Acwing 08

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

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

分析:在一维的基础上加一重循环即可;

#include
#include
using namespace std;
int main(){
    int N,M,V;
    cin >> N >> V >> M;
    vector<int> v(N+1),m(N+1),w(N+1);
    vectorint>> dp(M+1,vector<int>(V+1));
    for(int i = 1; i <= N; i++){
        cin >> v[i] >> m[i] >> w[i];
    }
    for(int i = 1; i <= N; i++){
        for(int j = M; j >= m[i]; j--)
            for(int k = V;k >= v[i]; k--)
                //是否选第i个物品
                dp[j][k] = max(dp[j][k],dp[j-m[i]][k-v[i]] + w[i]);
    }
    cout << dp[M][V];
}

6.分组背包问题 Acwing 09

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

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

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

输出最大价值。

思路:和多重背包有一些类似,多重背包是每个物品有si件,可以选0,1,2…si件。

而分组背包是不选,选第1个,或第2个或第3个…或第si个,都有si+1种决策方式,

即使用三层循环即可解决。没有优化方式。

#include
using namespace std;
int dp[104];
int w[104],v[104];
int main(){
    int N,V,s;
    cin >> N >> V;
    for(int i = 1; i <= N; i++){
        cin >> s;
        for(int j = 1; j <= s; j++)
            cin >> v[j] >> w[j];
        for(int j = V; j >= 0;j--){
            //从第i组中选一个
            for(int k = 1; k <= s; k++){
                if(j >= v[k]) dp[j] = max(dp[j],dp[j-v[k]]+w[k]);
            }
        }
    }
    cout << dp[V];
}

7.有依赖的背包问题 Acwing 10

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

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:

背包九讲整理_第2张图片

 

 

 

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N1…N。

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

输出最大价值。

链式前向星还没学,邻接矩阵更方便:

#include
#include
#include
using namespace std;
#define N 104
int mp[N][N];
int dp[N][N];
int n,m;
vector<int> v(N),w(N);
void dfs(int root){
    for(int i = 1; i <=n; i++){
        //下一个子节点在不同空间下可以看成是一个组,求出这个组;
        if(mp[root][i]) dfs(i);
        else continue;
        //分组背包问题,在第i组中选一个最大的
        for(int j = m-v[root]; j >=0;j--){//root一定要选,为root留一个位置
            for(int k = 0; k <= j; k++)
                dp[root][j] = max(dp[root][j],dp[root][j-k]+dp[i][k]);
        }
    }
    //一定要选root,选不了root的话,就直接为0;
    for(int j = m; j >= 0; j--)
        if( j >= v[root]) dp[root][j] = dp[root][j-v[root]] + w[root];
        else dp[root][j] = 0;
}
int main(){
    memset(mp,0,sizeof mp);
    cin >> n >> m;
    int p, root;
    //用邻接矩阵储存树,并找到根节点
    for(int i = 1;i <= n; i++){
        cin >> v[i] >> w[i] >>p;
        if( p == -1) root = i;
        else mp[p][i] = 1;
    }
    dfs(root);
    cout << dp[root][m];
}

 

8.背包问题求方案数 Acwing 11

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

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

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

输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。

只需加一个数组,然后在01背包基础上修改即可:

#include
#include
#include
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的情况下的最大价值。
    vector<int> p(V+1,1);//加一个数组表示方法数
    int mod = 1e9+7;
    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]);变为如下:
            {
                if(dp[j] < dp[j-v[i]]+w[i]){
                    dp[j] = dp[j-v[i]]+w[i];
                    p[j] = p[j-v[i]];
                }
                else if(dp[j] == dp[j-v[i]]+w[i]){
                    p[j] += p[j-v[i]];
                    p[j] %= mod;
                }
            }
    cout <<p[V];
}

9.背包问题求具体方案 Acwing 12

一维数组似乎不能解决;

#include
#include
using namespace std;
#define N 1004
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][N+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。
    int p[N+1][N+1];//用来记录路径
    memset(dp,0,sizeof(dp));
    memset(p,0,sizeof p);
    //用字典序排序时,要从后往前遍历
    for(int i = n; i >= 1; i--)
        for(int j = 1; j <= V; j++){
            //在没有max时要假设先不选
            dp[i][j] = dp[i+1][j];//这句容易忘,位置也要注意
            if(v[i] <= j){
                int temp = dp[i+1][j-v[i]] + w[i];
                if(temp >= dp[i][j]){
                    dp[i][j] = temp;
                    p[i][j] = 1;
                }
            }
        }
    //cout << dp[1][V] << endl;
    int vol = V;
    for(int i = 1; i <= n; i++){
        if(p[i][vol] == 1){
            cout << i << ' ';
            vol -= v[i];
        }
    }
}

 

你可能感兴趣的:(背包九讲整理)