背包问题
背包问题是动态规划中最经典的问题,很多题⽬或多或少都有背包问题的影⼦。它的基本形式是:给 定⼀组物品,每个物品有体积和价值,在不超过背包容量的情况下,选择物品使得总价值最⼤。 背包问题有多种变体,主要包括:
1. 01背包问题:每种物品只能选或不选(选0次或1次)。
2. 完全背包问题:每种物品可以选择⽆限次。
3. 多重背包问题:每种物品有数量限制。
4. 分组背包问题:物品被分为若⼲组,每组只能选⼀个物品。
5. 混合背包:以上四种背包问题混在⼀起。
6. 多维费⽤的背包问题:限定条件不⽌有体积,还会有其他因素(⽐如重量)。 除了经典的总价值最⼤问题,还会有:
1. ⽅案总数。
2. 最优⽅案。
3. ⽅案可⾏性。
4. 输出具体⽅案。 因此,背包问题种类⾮常繁多,题型⾮常丰富。但是,尽管背包有很多变形,都是从01背包问题演化 过来的。所以,⼀定要把01背包问题学好。
1 .【模板】01背包
二维版本
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int n, m;
int w[N], v[N];//记录体积和价值和
int f[N][N];//在体积不超过j的情况下选了i中的最大价值
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> w[i] >> v[i];
//问题一
//初始化:第0行,在选0个物品的情况下,不超过体积,价值为0
for (int i = 0; i <= m; i++)f[0][i] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)//背包问题中体积是可以从0开始的
{
f[i][j] = f[i - 1][j];//没选的情况下,就和f[i-1][j]一样
//选了
if (j >= w[i])
{
f[i][j] = max(f[i][j],f[i - 1][j - w[i]] + v[i]);//在选和不选之中选择最大值
}
}
}
cout << f[n][m] << endl;
//问题二,要求全部装满
//初始化:第一行选择0个的条件下,不满足装满的条件,可以设置为负无穷大
memset(f[0], -0x3f3f3f, sizeof(f[0]));
f[0][0] = 0;//0 0 是满足条件de
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i-1][j];
if (j >= w[i])
{
f[i][j] = max(f[i][j], f[i-1][j - w[i]] + v[i]);
}
}
}
if (f[n][m] < 0)cout << 0 << endl;
else
cout << f[n][m] << endl;
return 0;
}
第一问
不选
选
结果取最大值
i可以从1到n,而j需要从0 到m
第二问
不合法的情况下,初始化成-∞,
这样就只需要判断结果f[n][m]是否为负数,是那么就找不到
不是,就有结果
空间优化版本
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int n, m;
int w[N], v[N];//记录体积和价值和
int f[N];//在体积不超过j的情况下选了i中的最大价值
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> w[i] >> v[i];
//问题一
//初始化:第0行,在选0个物品的情况下,不超过体积,价值为0
for (int i = 0; i <= m; i++)f[i] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= w[i]; j--)//背包问题中体积是可以从0开始的
{
//选了
f[j] = max(f[j],f[j - w[i]] + v[i]);//在选和不选之中选择最大值
}
}
cout << f[m] << endl;
//问题二,要求全部装满
//初始化:第一行选择0个的条件下,不满足装满的条件,可以设置为负无穷大
memset(f, -0x3f3f3f, sizeof(f));
f[0] = 0;//0 0 是满足条件de
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= w[i]; j--)
{
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
if (f[m] < 0)cout << 0 << endl;
else
cout << f[m] << endl;
return 0;
}
(1)删掉第一维
(2)观察是否需要修改遍历顺序
2.P1048 [NOIP 2005 普及组] 采药 - 洛谷
#include<iostream>
using namespace std;
const int N = 1100;//时间超过1000,设置成100的哈,不符合
int t[N], v[N];
int f[N][N];//在不超过t时间的情况下,才n重要取得的最大价值
int main()
{
int T, M; cin >> T >> M;
for (int i = 1; i <= M; i++)cin >> t[i] >> v[i];
//属于01背包的第一种问题,不需要初始化也可以
for (int i = 1; i <= M; i++)
{
for (int j = 0; j <= T; j++)
{
f[i][j] = f[i - 1][j];
if (j >= t[i])
{
f[i][j] = max(f[i][j], f[i - 1][j - t[i]] + v[i]);
}
}
}
cout << f[M][T] << endl;
return 0;
}
//空间优化
#include<iostream>
using namespace std;
const int N = 1100;//时间超过1000,设置成100的哈,不符合
int t[N], v[N];
int f[N];//在不超过t时间的情况下,才n重要取得的最大价值
int main()
{
int T, M; cin >> T >> M;
for (int i = 1; i <= M; i++)cin >> t[i] >> v[i];
//属于01背包的第一种问题,不需要初始化也可以
for (int i = 1; i <= M; i++)
{
for (int j = T; j >= t[i]; j--)
{
if (j >= t[i])
{
f[j] = max(f[j], f[j - t[i]] + v[i]);
}
}
}
cout << f[T] << endl;
return 0;
}
3.P1164 小A点菜 - 洛谷
#include<iostream>
using namespace std;
const int N = 110;//菜的种类
const int M = 1010;//多少钱
int v[N];
int n, m;
int f[N][M];//在正好用M元的情况下,点的n种菜,的方案数
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> v[i];
//初始化,第一行中。
f[0][0] = 1;//用0元买0菜,是一种方案,
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if (j >= v[i])
{
f[i][j] += f[i - 1][j - v[i]];
}
}
}
cout << f[n][m] << endl;
return 0;
}
总方案数是两者累加在一起,而不是取最大值
有:就++
没有:那就不要了
0 0 方案数为1
4.P2946 [USACO09MAR] Cow Frisbee Team S - 洛谷
状态表示如果记录为能力值总和的话,会太大了
只需要记录取模运算为0 的即可。
不选就找上面的
选,就找[j - a[i]]的,注意,这里会出现负数的情况,但在这道题中,负数也是有意义的。
比方说,我们要凑 j = 1,a[i] = 9, a[i]%5 == 4, j - a[i] = -3,我们本来是要找-3的位置的,使得取模 == 1但是我们是找不到的,但是我们可以反向的向前找2,4+2 == 6 ,取模 == 1。
取模运算
初始化,0 0 ,结果要把 0 0 减去
#include<iostream>
using namespace std;
const int MOD = 1e8;
const int N = 2010;
const int M = 1010;
int n, m;//马的数量和幸运值
int v[N];//能力值
int f[N][M];//前1~i个马中,选出的能力值总和模幸运值的方案数
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> v[i];
//初始化
f[0][0] = 1;//选0个马,得出的能力值模幸运值为0
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < m; j++)
{
f[i][j] = (f[i - 1][j] + f[i - 1][((j - v[i]) % m + m) % m]) % MOD;
}
}
cout << f[n][0] - 1 << endl;//把 0 0 减去
return 0;
}
2. 完全背包问题
2.1【模板】完全背包
根据f[i][j]的例子推导出下面的式子:f[i][j-v[i]];
然后用f[i][j-b[i]]+w[i]替代max的第二部分!!!!!!!!
同01背包问题简直一模一样
二维化版本
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int f[N][N];
int n, m;
int w[N], v[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if (j >= w[i])
{
f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);
}
}
}
cout << f[n][m] << endl;
//初始化
memset(f[0], -0x3f3f3f3f, sizeof(f[0]));
f[0][0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if (j >= w[i])
{
f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);
}
}
}
if (f[n][m] < 0)cout << 0 << endl;
else cout << f[n][m] << endl;
}
空间优化版本
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int f[N];
int n, m;
int w[N], v[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
{
for (int j = w[i]; j <= m; j++)
{
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
cout << f[m] << endl;
//初始化
memset(f, -0x3f3f3f3f, sizeof(f));
f[0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = w[i]; j <= m; j++)
{
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
if (f[m] < 0)cout << 0 << endl;
else cout << f[m] << endl;
}
2.2记录详情 - 洛谷 | 计算机科学教育新生态
#include<iostream>
using namespace std;
typedef long long LL;//算一下总价值达到了10^11,太大了,要用LL
const int T = 1e7 + 10;
const int N = 1e4 + 10;
int n, m;//种类,时间
int t[N], v[N];
LL f[T];//数组太大,空间优化,
int main()
{
//空间优化版本
cin >> m >> n;
for (int i = 1; i <= n; i++)cin >> t[i] >> v[i];
for (int i = 1; i <= n; i++)
{
for (int j = t[i]; j <= m; j++)
{
f[j] = max(f[j], f[j - t[i]] + v[i]);
}
}
cout << f[m] << endl;
}
2.3P2918 [USACO08NOV] Buying Hay S - 洛谷
#include<iostream>
#include<cstring>
using namespace std;
const int N = 110;
const int M = 50010;
int p[N], c[N];
int f[N][M];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> p[i] >> c[i];
//初始化
memset(f[0], 0x3f3f3f3f, sizeof(f[0]));
f[0][0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = min(f[i-1][j], f[i][max(0, j - p[i])] + c[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
维持>=t的条件是以,刚好为基础,只不过加上了可以超过但置为下标0 的条件
。设置无穷大,用max(0 , f[][])来设防
2.4P5662 [CSP-J2019] 纪念品 - 洛谷
股票问题:都可以转化成某天卖,隔天卖的过程
股票问题中的重要结论:
任何一笔跨天的交易,都可以转化成“某天买,隔天卖”的形式不用考虑:某天买,那天卖?呢
连续,不要断开。
连续两天的交易情况,一定要连续。不可以断开。
转化成背包问题,在金币不超过m的情况的;f[[i][j]记录的是最大利润
记得返回的时候是利润+本钱
#include<iostream>
#include<cstring>
using namespace std;
const int N = 110;
const int M = 10100;
int t, n, m;
int f[N][M];//在金币不超过m的情况的;f[[i][j]记录的是最大利润
int p[N][N];//记录某天的商品价格
int solve(int w[], int v[], int m)
{
memset(f, 0, sizeof(f));//记得初始化
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];//不买
if (j >= w[i])//卖
{
f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i] - w[i]);//花的钱+对应的利润
}
}
}
return f[n][m]+m;//返回本钱加利润
}
int main()
{
cin >> t >> n >> m;
for (int i = 1; i <= t; i++)
{
for (int j = 1; j <= n;j++)
{
cin >> p[i][j];
}
}
for (int i = 1; i < t; i++)
{
//股票的结论任何一笔跨天的交易,都可以转化成“某天买,隔天卖”的形式
//不用考虑:某天买,那天卖,直接连续不间断的买卖,有利润就卖,没利润就留着
m = solve(p[i], p[i + 1], m);
}
cout << m << endl;
return 0;
}
3.多重背包问题
3.1多重背包
这里看着像是完全背包,但其实并不能像完全背包的方法进行优化。
因为完全背包是可以取无限个,最后的k1 实际上是可以等于 k2的。
但多重背包就不行,由于是有限个的,就肯能,导致,不能让替代成功。所以说不行
但是可以使用二进制进行优化
#include<iostream>
using namespace std;
const int N = 110;
int n, t;//物品种类和承重
int x[N], w[N], v[N];//物品数量,重量,价值
int f[N][N];//【1,i】之间,总重量buchaoguo t的条件下,的最大价值
int main()
{
cin >> n >> t;
for (int i = 1; i <= n; i++)cin >> x[i] >> w[i] >> v[i];
//完全背包问题的实现
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= t; j++)
{
f[i][j] = f[i - 1][j];
for (int k = 0; k <= x[i]; k++)//做不了优化,只能全部拿出来一个一个地试
{
if (j >= k * w[i])
f[i][j] = max(f[i][j], f[i - 1][j - k * w[i]] + k * v[i]);
else break;
}
}
}
cout << f[n][t] << endl;
return 0;
}
空间优化
#include<iostream>
using namespace std;
const int N = 110;
int n, t;//物品种类和承重
int x[N], w[N], v[N];//物品数量,重量,价值
int f[N];//【1,i】之间,总重量buchaoguo t的条件下,的最大价值
int main()
{
cin >> n >> t;
for (int i = 1; i <= n; i++)cin >> x[i] >> w[i] >> v[i];
//完全背包问题的实现
for (int i = 1; i <= n; i++)
{
for (int j = t; j >= 0; j--)
{
for (int k = 0; k <= x[i]; k++)//做不了优化,只能全部拿出来一个一个地试
{
if (j >= k * w[i])
f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
else break;
}
}
}
cout << f[t] << endl;
return 0;
}
二进制优化
多重背包问题的二级制优化:可以把多重背包转化成01背包。
n*m*x ---> n*m*logx
!!!二进制优化不是万能的,求最大数可以使用,求方案数就不能使用。
#include <iostream>
using namespace std;
const int N = 110 * 5;
int n, m;
int w[N], v[N], pos;
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
int x, y, z; cin >> x >> y >> z;
// 按照⼆进制拆分
int t = 1;
while (x >= t)
{
pos++;
w[pos] = t * y;
v[pos] = t * z;
x -= t;
t *= 2;
}
if (x) // 处理剩余
{
pos++;
w[pos] = x * y;
v[pos] = x * z;
}
}
// 针对拆分之后的物品,做⼀次 01背包即可
for (int i = 1; i <= pos; i++)
for (int j = m; j >= w[i]; j--)
f[j] = max(f[j], f[j - w[i]] + v[i]);
cout << f[m] << endl;
return 0;
}
3.2P1077 [NOIP 2012 普及组] 摆花 - 洛谷
#include<iostream>
using namespace std;
int n, m;
const int N = 1010;
const int Mod = 1e6 + 7;
int a[N];
int f[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> a[i];
f[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];//不选,把这一步写出来,不要省略
for (int k = 1; k <= a[i]; k++)//选
{
if (j >= k)
{
f[i][j] = (f[i][j] + f[i - 1][j - k]) % Mod;//累加取模
}
else
{
break;
}
}
}
}
cout << f[n][m] << endl;
return 0;
}
空间优化
#include<iostream>
using namespace std;
int n, m;
const int N = 1010;
const int Mod = 1e6 + 7;
int a[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)cin >> a[i];
f[0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= 0; j--)
{
for (int k = 1; k <= a[i]; k++)//选
{
if (j >= k)
{
f[j] = (f[j] + f[j - k]) % Mod;//累加取模
}
else
{
break;
}
}
}
}
cout << f[m] << endl;
return 0;
}
4.分组背包
4.1P1757 通天之分组背包 - 洛谷
#include<iostream>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3 + 10;
int n, m;//输入组数,承重
vector<PII> g[N];//这个数据结构感觉很重要的,g[N]记录有多少组,里面用PII记录重量和价值
int f[N][N];//[1,i]组内,不超过m的情况下,最大价值
int main()
{
cin >> m >> n;
int cnt = 0;//找组数
for (int i = 1; i <= n; i++)
{
int a, b, c; cin >> a >> b >> c;
cnt = max(cnt, c);
g[c].push_back({ a,b });//是几号,就对应接到对应的几号上去
}
for (int i = 1; i <= cnt; i++)
{
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
for (auto& x : g[i])
{
int a = x.first, b = x.second;
if (j >= a)
{
f[i][j] = max(f[i][j], f[i - 1][j - a] + b);
}
}
}
}
cout << f[cnt][m] << endl;
return 0;
}
4.2P5322 [BJOI2019] 排兵布阵 - 洛谷
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110,M = 2e4+10;
int s, n, m;
int a[N][N];
int f[N][M];
int main()
{
cin >> s >> n >> m;
for(int i = 1;i <= s;i++)//玩家数
for (int j = 1; j <= n; j++)//城池数
{
cin >> a[j][i];//反着存
//贪心
a[j][i] = 2 * a[j][i] + 1;//两倍+1可以拿下,先记录下,后遍历,拿到就攻下来
}
//排序
for (int i = 1; i <= n; i++)
sort(a[i]+1, a[i] + s + 1);//每个城池存着 s个玩家的派兵人数,对他们进行排序
for (int i = 1; i <= n; i++)//城池
{
for (int j = 0; j <= m; j++)//手里的士兵
{
f[i][j] = f[i - 1][j];//不要
for (int k = 1; k <= s; k++)//要
{
if (j >= a[i][k])
{
f[i][j] = max(f[i][j], f[i - 1][j - a[i][k]] + k * i);
}
}
}
}
cout << f[n][m] << endl;
return 0;
}
5.混合背包
//二维形式
#include<iostream>
using namespace std;
const int N = 1e4 + 10;
const int M = 1e3 + 10;
int ts_h, ts_m, te_h, te_m;
char s1, s2;
int n;
int t[N], c[N], p[N];
int f[N][M];
int main()
{
cin >> ts_h >> s1 >> ts_m >> te_h >> s2 >> te_m;
int time = te_h * 60 + te_m - ts_h * 60 - ts_m;
cin >> n;
for (int i = 1; i <= n; i++)cin >> t[i] >> c[i] >> p[i];
for (int i = 1; i <= n; i++)
{
if (p[i] == 1)//01背包
{
for (int j = 0; j <= time; j++)
{
f[i][j] = f[i - 1][j];
if (j >= t[i])
{
f[i][j] = max(f[i][j], f[i - 1][j - t[i]] + c[i]);
}
}
}
else if (p[i] == 0)//完全背包问题
{
for (int j = 0; j <= time; j++)
{
f[i][j] = f[i - 1][j];
if(j >= t[i])
f[i][j] = max(f[i - 1][j], f[i][j - t[i]] + c[i]);
}
}
else//多重背包问题
{
for (int j = 0; j <= time; j++)
{
f[i][j] = f[i - 1][j];
for (int k = 1; k <= p[i]; k++)
{
if (j >= k * t[i])
{
f[i][j] = max(f[i][j], f[i - 1][j - k * t[i]] + k * c[i]);
}
}
}
}
}
cout << f[n][time] << endl;
return 0;
}
//优化形式
//#include<iostream>
//
//using namespace std;
//
//const int N = 1e4 + 10;
//const int M = 1e3 + 10;
//
//int ts_h, ts_m, te_h, te_m;
//char s1, s2;
//int n;
//int t[N], c[N], p[N];
//int f[M];
//
//int main()
//{
// cin >> ts_h >> s1 >> ts_m >> te_h >> s2 >> te_m;
// int time = te_h * 60 + te_m - ts_h * 60 - ts_m;
// cin >> n;
// for (int i = 1; i <= n; i++)cin >> t[i] >> c[i] >> p[i];
//
// for (int i = 1; i <= n; i++)
// {
// if (p[i] == 1)//01背包
// {
// for (int j = time; j >= t[i]; j--)//大到小
// {
//
// f[j] = max(f[j], f[j - t[i]] + c[i]);
//
// }
// }
// else if (p[i] == 0)//完全背包问题
// {
// for (int j = t[i]; j <= time; j++)//小到大
// {
// f[j] = max(f[j], f[j - t[i]] + c[i]);
// }
// }
// else//多重背包问题
// {
// for (int j = time; j >= t[i]; j--)
// {
// for (int k = 1; k <= p[i]; k++)
// {
// if (j >= k * t[i])
// {
// f[j] = max(f[j], f[j - k * t[i]] + k * c[i]);
// }
// }
// }
// }
// }
// cout << f[time] << endl;
// return 0;
//}
6.P1910 L 国的战斗之间谍 - 洛谷(多维背包)
相对于01背包只是多了个限制条件罢了
第二个注意点如果不进行空间优化的话,会空间爆掉 100*1000*1000
//没有优化
// #include<iostream>
//
//using namespace std;
//
//const int N = 110, M = 1010;
//
//int a[N], b[N],c[N];
//int f[N][M][M];
//
//int n, m, x;
//int main()
//{
// cin >> n >> m >> x;
//
// for (int i = 1; i <= n; i++)cin >> a[i] >> b[i] >> c[i];
//
//
// for (int i = 1; i <= n; i++)
// {
// for (int j = 0; j <= m; j++)
// {
// for (int k = 0; k <= x; k++)
// {
// f[i][j][k] = f[i - 1][j][k];
// if (j >= b[i] && k >= c[i])
// {
// f[i][j][k] = max(f[i][j][k], f[i - 1][j - b[i]][k - c[i]]+a[i]);
// }
// }
// }
// }
// cout << f[n][m][x] << endl;
// return 0;
//}
//空间优化
#include<iostream>
using namespace std;
const int N = 110, M = 1010;
int a[N], b[N], c[N];
int f[M][M];
int n, m, x;
int main()
{
cin >> n >> m >> x;
for (int i = 1; i <= n; i++)cin >> a[i] >> b[i] >> c[i];
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= b[i]; j--)
{
for (int k = x; k >= c[i]; k--)
{
if (j >= b[i] && k >= c[i])
{
f[j][k] = max(f[j][k], f[j - b[i]][k - c[i]] + a[i]);
}
}
}
}
cout << f[m][x] << endl;
return 0;
}