【DP学习总结】背包九讲(模型)

01背包

问题描述:

N N N件物品和一个容量为 V V V的背包。第 i i i件物品的体积是 v [ i ] v[i] v[i],价值是 w [ i ] w[i] w[i]。求解将哪些物品装入背包可使价值总和最大。

思路:

01背包的特点:每件物品可以选择放或者不放。

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品,容量不超过 j j j的所有方案数中价值的最大值
  • 状态计算:不选: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f[i][j]=f[i1][j],选: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] ) + w [ i ] ) f[i][j] = max(f[i][j], f[i - 1][j - v[i]) + w[i]) f[i][j]=max(f[i][j],f[i1][jv[i])+w[i])

Code:

cin >> n >> m;
    for(int i = 1; i <= n; i++) {
		cin >> v >> w;
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i - 1][j];
            if(j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w);
        }           
}
cout << f[n][m] << endl;

优化:

  1. 滚动数组优化:
    观察到,对于第 i i i层都只和第 i − 1 i-1 i1层的状态有关。优化如下:
cin >> n >> m;
    for(int i = 1; i <= n; i++) {
		cin >> v >> w;
        for(int j = 1; j <= m; j++)
        {
           f[i & 1][j] = f[i - 1 & 1][j];
            if(j >= v) f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - v] + w);
        }           
}
cout << f[n & 1][m] << endl;
  1. 经典01背包优化:
    f [ j ] f[j] f[j]来表示背包容量为 j j j的最大价值
    在更新状态的时候,我们需要逆序枚举体积(m->v),
    为什么要逆序?优化为1维的时候,对于第选 i i i个物品体积为 v v v,则 f [ j ] f[j] f[j] f [ j − v ] f[j-v] f[jv]得来,原本用到的应该是二维的 f [ i − 1 ] [ j − v ] f[i-1][j - v] f[i1][jv],如果一维去正序枚举的话用到的就是 f [ i ] [ j − v ] f[i][j - v] f[i][jv]。如果是逆序得话,就可以避免了。
cin >> n >> m;
    for(int i = 1; i <= n; i++) {
		cin >> v >> w;
		for(int j = m; j >= v; -- j)
			f[j] = max(f[j], f[j - v] + w); 
}
cout << f[m] << endl;

完全背包

问题描述:

N N N种物品和一个容量为 V V V的背包,每种物品都有无限件可用,第 i i i种物品的体积是 v [ i ] v[i] v[i],价值是 w [ i ] w[i] w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j]:表示前 i i i个物品,体积不超过 j j j的所有方案中价值的最大值
  • 状态计算: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − k ∗ v ] + k ∗ w ) ( j − k ∗ v ≥ 0 ) f[i][j] = max(f[i][j], f[i - 1][j - k*v] + k*w)(j-k*v\ge 0) f[i][j]=max(f[i][j],f[i1][jkv]+kw)(jkv0)

优化一下状态计算:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v ] + w , f [ i − 1 ] [ j − 2 v ] + 2 w , ⋯ f [ i − 1 ] [ k ∗ v ] + k ∗ w ) f[i][j] = max(f[i - 1][j], \quad f[i-1][j -v] + w, f[i-1][j-2v]+2w, \cdots f[i-1][k*v]+k*w) f[i][j]=max(f[i1][j],f[i1][jv]+w,f[i1][j2v]+2w,f[i1][kv]+kw)
f [ i ] [ j − v ] = m a x ( f [ i − 1 ] [ j − v ] , f [ i − 1 ] [ j − 2 v ] + w , f [ i − 1 ] [ j − 3 v ] + 2 w , ⋯ f [ i − 1 ] [ k ∗ v ] + ( k − 1 ) ∗ w ) ⋯ f[i][j - v] = max( \qquad \quad \quad f[i - 1][j-v], \quad f[i-1][j -2v] + w, f[i-1][j-3v]+2w, \cdots f[i-1][k*v]+(k-1)*w)\\ \cdots f[i][jv]=max(f[i1][jv],f[i1][j2v]+w,f[i1][j3v]+2w,f[i1][kv]+(k1)w)

所以 状态计算可以写成: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − v ] + w ) f[i][j] = max(f[i-1][j], f[i][j-v]+w) f[i][j]=max(f[i1][j],f[i][jv]+w)
code:

for(int i = 1 ; i <=n ;i++) {
	cin >> v >> w;
	for(int j = 0 ; j <=m ;j++)
	{
  	  	f[i][j] = f[i - 1][j];
    	if(j - v >= 0)  f[i][j] = max(f[i][j],f[i][j - v] + w);
	}
}
cout << f[n][m] << endl;

优化:

  1. 滚动数组优化:
for(int i = 1 ; i <=n ;i++) {
	cin >> v >> w;
	for(int j = 0 ; j <= m ;j++)
	{
  	  	f[i & 1][j] = f[i - 1 & 1][j];
    	if(j - v >= 0)  f[i & 1][j] = max(f[i & 1][j],f[i & 1][j - v] + w);
	}
}
cout << f[n & 1][m] << endl;
  1. 经典完全背包优化:
    f [ j ] f[j] f[j]表示容量为 j j j的背包的能得到的最大价值
    计算:从小到大去计算(与01背包的区别)
for(int i = 1 ; i <=n ;i++) {
	cin >> v >> w;
	for(int j = v ; j <= m ;j++)
	{
  	  	f[j] = max(f[j], f[j - v] + w);
	}
}
cout << f[m] << endl;

多重背包

问题描述:

N N N种物品和一个容量为 V V V的背包。第 i i i种物品最多有 s [ i ] s[i] s[i]件可用,每件体积是 v [ i ] v[i] v[i],价值是 w [ i ] w[i] w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

思路:

  • 状态表示: f [ j ] f[j] f[j]:体积不超过 j j j的所有方案中价值的最大值
  • 状态计算: f [ j ] = m a x ( f [ j ] , f [ j − v ] + w ) f[j] = max(f[j], f[j - v]+ w) f[j]=max(f[j],f[jv]+w) (对于第 i i i件物品,只需做 s i s_i si次01背包即可)

code:

for(int i = 1; i <= n; ++i)
    {
        int v, w, s; cin >> v >> w >> s;
        for(int k = 1; k <= s; ++k) // 对s次都做一次01背包
        for(int j = m; j >= v; --j)
            f[j] = max(f[j], f[j - v] + w);
    }
    cout << f[m];

优化:

  1. 二进制优化:对于一个数 x x x,我们把它分成 2 0 , 2 1 , 2 2 , ⋯   , 2 t ( 2 t < = x ) , x − 2 t 2^0,2^1, 2^2, \cdots, 2^t(2^t <= x),x - 2^t 2021,22,,2t(2t<=x),x2t 。容易发现,对于 0 ∼ x 0 \sim x 0x都可以用上述拼出来。那么进行 s i s_i si次的次数就减少到了 l o g 2 s i log_{2}s_i log2si
    cin >> n >> m;
    vector<Good>g;
    for(int i = 1; i <= n; ++i)
    {
        int v, w, s; cin >> v >> w >> s;
        for(int k = 1; k <= s; k <<= 1)
        {
            s -= k;
            g.push_back({k*v,k*w});
        }
        if(s > 0) g.push_back({s*v,s*w});
    }
    for(auto goods : g)
    for(int j = m; j >= goods.v; --j)
        f[j] = max(f[j], f[j-goods.v] + goods.w);
    cout << f[m];
  1. 单调队列优化

混合背包

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

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 s i s_i si 次(多重背包);

每种体积是 v i v_i vi,价值是 w i w_i wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

solution: 对应物品做相应背包即可
题目连接:Acwing 7
code:

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) {
        int v, w, s;
        cin >> v >> w >> s;
        if(s == 0) {
            for(int j = v; j <= m; ++ j)    
                f[j] = max(f[j], f[j - v] + w);
        }else {
            if(s == -1) {
                for(int j = m; j >= v; -- j) f[j] = max(f[j], f[j - v] + w);
            }else {
                vector<pair<int, int>> good;
                for(int k = 1; k <= s; k <<= 1) {
                    s -= k;
                    good.push_back({k * v, k * w});
                }
                if(s > 0) good.push_back({s * v, s * w});
                for(auto Good : good) 
                    for(int j = m; j >= Good.first; -- j) f[j] = max(f[j], f[j - Good.first] + Good.second);
            }
        }
    }
    cout << f[m] << "\n";
}

分组背包

问题描述:
题目链接: Acwing 9
N N N 组物品和一个容量是 V V V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 v i , j v_{i,j} vi,j,价值是 w i , j w_{i,j} wi,j,其中 i i i 是组号, j j j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。数据范围均小于100

思路:
对每一组进行01背包
Code:

#include<bits/stdc++.h>
using namespace std;
int n, m;
int f[110], v[110][110], w[110][110], s[110];
int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n; ++i)
    {
        cin >> s[i];
        for(int j=1;j<=s[i]; ++j)
            cin >> v[i][j] >> w[i][j];
    }
    for(int i = 1; i <= n; ++i)// 枚举组,然后01背包
    {
        for(int k = m; k >= 0; --k)//每个h对应每组最优解
        {
            for(int h = 1; h <= s[i]; ++h)
            if(k >= v[i][h]) f[k] = max(f[k], f[k - v[i][h] ] + w[i][h]);
        }
    }
    cout << f[m];
    return 0;
}

二维费用背包

题目连接:Acwing 8
N N N 件物品和一个容量是 V V V 的背包,背包能承受的最大重量是 M M M
每件物品只能用一次。体积是 v i v_i vi,重量是 m i m_i mi,价值是 w i w_i wi
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

思路:
将一维的01背包扩展为二维

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int f[N][N];
int n, v, m;
int main()
{
    cin >> n >> v >> m;
    for(int i = 1; i <= n; ++ i) {
        int v_, m_, w_;
        cin >> v_ >> m_ >> w_;
        for(int j = v; j >= v_; -- j)   
            for(int k = m; k >= m_; -- k)
                f[j][k] = max(f[j][k], f[j - v_][k - m_] + w_);
    }
    cout << f[v][m] << "\n";
}

有依赖的背包

题目连接:Acwing 10
在这里插入图片描述

树形DP

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示,考虑以 i i i根节点的子树,选上 i i i,且体积不超过 j j j的所有方案价值的最大值
  • 状态计算:将以 i i i为根节点的子树按照体积划分状态,也就是分给根节点下面的体积 f [ u ] [ j ] = m a x ( f [ u ] [ j ] , f [ u ] [ j − k ] + f [ s o n ] [ k ] ) f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]) f[u][j]=max(f[u][j],f[u][jk]+f[son][k])结合代码理解QAQ
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
vector<int> G[N];
int f[N][N], v[N], w[N], n, m;

void dfs(int u) {
    
    for(int x : G[u]) {
        dfs(x); // 从下往上算
        for(int j = m - v[u]; j >= 0; -- j) // 枚举一下需要更新的状态
            for(int k = 0; k <= j; ++ k) // 枚举一下分给i根节点下面的体积
                f[u][j] = max(f[u][j], f[u][j - k] + f[x][k]);
    }
    // 选上了第u件
    for(int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u];
    for(int j = 0; j < v[u]; ++ j ) f[u][j] = 0;  // 小于就不存在以u为根节点的情况
}

int main()
{
    cin >> n >> m;
    int root, p;
    for(int i = 1; i <= n; ++ i) {
        cin >> v[i] >> w[i] >> p;
        if(p == -1) root = i;
        else G[p].push_back(i);
    }
    dfs(root);
    cout << f[root][m] << "\n";
    return 0;
}

背包问题求具体方案

题目链接:Acwing 12
题意:

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。
i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1 ⋯ N 1 \cdots N 1N

solution:
要求方案,其实就是求一下状态转移的路径。也就是说,对于第 i i i件物品,是选了还是没有选。倒着退一下就好
因为本题要求字典序最小的方案,在从 1 ∼ n 1 \sim n 1n推路径的时候,遇到可以选要先选。所有采用 n ∼ 1 n \sim 1 n1来进行背包DP

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j], 表示考虑后 i i i件物品,体积不超过 j j j的所有方案的价值的最大值
  • 状态转移: 不选: f [ i ] [ j ] = f [ i + 1 ] [ j ] f[i][j] = f[i + 1][j] f[i][j]=f[i+1][j],选 f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i + 1 ] [ j − v ] + w ) f[i][j] = max(f[i][j], f[i + 1][j - v] + w) f[i][j]=max(f[i][j],f[i+1][jv]+w)
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N], path[N], cnt;
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i) cin >> v[i] >> w[i];
    for(int i = n; i >= 1; -- i) {
        for(int j = 0; j <= m; ++ j) {
            f[i][j] = f[i + 1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
        }
    }
    for(int i = 1, j = m; i <= n; ++ i) {// 能选就先选
        if(j >= v[i] && f[i + 1][j - v[i]] + w[i] == f[i][j]) {
            path[cnt ++ ] = i;
            j -= v[i];
        }
    }
    for(int i = 0; i < cnt; ++ i) cout << path[i] << " ";
}

背包问题求具体方案数

题目链接:Acwing 11

题意:
N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。
i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 1 0 9 + 7 10^9 + 7 109+7 的结果。

solution:
01背包模型:

  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示考虑前 i i i个物品,体积不超过 j j j的所有方案当中的最大值
  • 状态计算: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i1][j], f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v ] + w ) f[i][j] = max(f[i][j], f[i - 1][j - v] + w) f[i][j]=max(f[i][j],f[i1][jv]+w)

路径:

  • 状态表示: g [ i ] [ j ] g[i][j] g[i][j]表示前 i i i个物品,体积为 j j j且价值大的方案数
  • 状态计算: g [ i ] [ j ] = g [ i ] [ j ] + g [ i − 1 ] [ j ] g[i][j] = g[i][j] + g[i - 1][j] g[i][j]=g[i][j]+g[i1][j] g [ i ] [ j ] = g [ i ] [ j ] + g [ i − 1 ] [ j − v ] ) g[i][j] = g[i][j] + g[i - 1][j - v]) g[i][j]=g[i][j]+g[i1][jv])

code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N][N], g[N][N];
int n, m, v[N], w[N];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i) {
        cin >> v[i] >> w[i];
        for(int j = 0; j <= m; ++ j) {
            f[i][j] = f[i - 1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }
    g[0][0] = 1;
    for(int i = 1; i <= n; ++ i) {
        for(int j = 0; j <= m; ++ j) {
            if(f[i][j] == f[i - 1][j]) {
                g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
            }
            if(j >= v[i] && f[i - 1][j - v[i]] + w[i] == f[i][j]) {
                g[i][j] = (g[i][j] + g[i - 1][j - v[i]]) % mod;
            }
        }
    }
    int res = 0;
    for(int i = 0; i <= m; ++ i) {
        if(f[n][i] == f[n][m]) {
            res = (res + g[n][i]) % mod;
        }
    }
    cout << res << "\n";
}

背包的例题

相关应用:背包例题

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W⁡angduoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值