1.二维费用的背包问题
有 N N N件物品和一个容量是 V V V的背包,背包能承受的最大重量是 M M M。
每件物品只能用一次。体积是 v i v_{i} vi,重量是 m i m_{i} mi,价值是 w i w_{i} wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数, N , V , M , N,V,M, N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N N N行,每行三个整数 v i , v_{i}, vi, m i , m_{i}, mi, w i w_{i} wi,用空格隔开,分别表示第 i i i件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
≤
1000
0<N≤1000
0<N≤1000
0
<
V
,
M
≤
100
0<V,M≤100
0<V,M≤100
0
<
v
i
,
m
i
≤
100
0<v_{i},m_{i}≤100
0<vi,mi≤100
0
<
w
i
≤
1000
0<w_{i}≤1000
0<wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
1.1题解
与01背包、完全背包、多重背包有很大相似点,只是在考虑状态计算有所差异而已。
1.2代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
//n件物品,总体积为V,总重量为W
int n,V,M;
//优化为两维
int f[N][N];
int main()
{
cin >> n >> V >> M;
for(int i = 0; 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]<<endl;
return 0;
}
2.潜水员
潜水员为了潜水要使用特殊的装备。
他有一个带 2 2 2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。
潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。
潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有 5 5 5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要 5 5 5升的氧和 60 60 60升的氮则总重最小为 249 249 249( 1 1 1, 2 2 2或者 4 4 4, 5 5 5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入格式
第一行有
2
2
2个整数
m
,
n
m,n
m,n。它们表示氧,氮各自需要的量。
第二行为整数 k k k表示气缸的个数。
此后的 k k k行,每行包括 a i , b i , c i , 3 a_{i},b_{i},c_{i},3 ai,bi,ci,3个整数。这些各自是:第 i i i个气缸里的氧和氮的容量及气缸重量。
输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
数据范围
1
≤
m
≤
21
,
1≤m≤21,
1≤m≤21,
1
≤
n
≤
79
,
1≤n≤79,
1≤n≤79,
1
≤
k
≤
1000
,
1≤k≤1000,
1≤k≤1000,
1
≤
a
i
≤
21
,
1≤a_{i}≤21,
1≤ai≤21,
1
≤
b
i
≤
79
,
1≤b_{i}≤79,
1≤bi≤79,
1
≤
c
i
≤
800
1≤c_{i}≤800
1≤ci≤800
输入样例:
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
输出样例:
249
2.1题解
算法分析
- 状态表示
f[i,j,k]
:所有从前i
个物品中选,且氧气含量至少是j
,氮气含量至少是k
的所有选法的气缸重量总和的最小值 - 状态计算:
- 所有不包含物品i的所有选法:
f[i - 1,j,k]
- 所有包含物品i的所有选法:
f[i - 1,j - v1,k - v2]
- 所有不包含物品i的所有选法:
注意:即使所需要的氧气或者氮气所需的是数量是负数,但其所需数量与0
是等价的,因此可以通过所需数量为0
来转移
扩展
可能很多人会有这样的疑问,二维费用的背包问题的状态转移方程代码如下
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);
而本题的状态转移方程代码如下
for(int j = V;j >= 0;j --)
for(int k = M;k >= 0;k --)
f[j][k] = min(f[j][k], f[max(0, j - v)][max(0, k - m)] + w);
为什么上面的代码 j
只需要遍历到v
,k
只能遍历到m
。而下面的代码 j
还需要遍历到0
,k
还需要遍历到0
?同时为什么氧气或者氮气所需的是数量是负数时,可以与数量0
的状态等价?
解答:对比两题的思路,二维费用的背包问题,求的是不能超过体积V
,重量M
的情况下,能拿到价值的最大值。而本题是至少需要体积V
,重量M
的情况下,能拿到价值的最小值。就拿体积来说,至少需要多少体积,也就是说有体积比需要的体积大的物品还是能用得到,例如f[3][5]
,至少需要3
个体积,5
个重量,求能拿到价值的最小值,现在只有一个物品,体积是4
,重量是4
,价值w
,它说至少需要3
个体积,那么体积是4
还是可以用到,只是多了1
个体积没用占着而已,不影响其价值。因此若用了这个物品,则变成了求f[0][1] + w
,表示体积已经不再需求了,只需要0
个体积即可
求最大值最小值初始化总结
二维情况
1、体积至多j
,f[i,k] = 0
,0 <= i <= n
, 0 <= k <= m
(只会求价值的最大值)
2、体积恰好j
,
当求价值的最小值:f[0][0] = 0
, 其余是INF
当求价值的最大值:f[0][0] = 0
, 其余是-INF
3、体积至少j
,f[0][0] = 0
,其余是INF
(只会求价值的最小值)
一维情况
1、体积至多j
,f[i] = 0
, 0 <= i <= m
(只会求价值的最大值)
2、体积恰好j
,
当求价值的最小值:f[0] = 0
, 其余是INF
当求价值的最大值:f[0] = 0
, 其余是-INF
3、体积至少j
,f[0] = 0
,其余是INF
(只会求价值的最小值)
2.2代码实现
#include <cstring>
#include <iostream>
using namespace std;
const int N = 22, M = 80;
int n, m, K;
int f[N][M];
int main()
{
cin >> n >> m >> K;
//初始化正无穷的目的是我们在更新的时候不使用这个值
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
while (K -- )
{
int v1, v2, w;
cin >> v1 >> v2 >> w;
for (int i = n; i >= 0; i -- )
for (int j = m; j >= 0; j -- )
f[i][j] = min(f[i][j], f[max(0, i - v1)][max(0, j - v2)] + w);
}
cout << f[n][m] << endl;
return 0;
}
3.数字组合
给定 N N N 个正整数 A 1 , A 2 , … , A N A_{1},A_{2},…,A_{N} A1,A2,…,AN,从中选出若干个数,使它们的和为 M M M,求有多少种选择方案。
输入格式
第一行包含两个整数
N
N
N和
M
M
M。
第二行包含 N N N个整数,表示 A 1 , A 2 , … , A N A_{1},A_{2},…,A_{N} A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1
≤
N
≤
100
,
1≤N≤100,
1≤N≤100,
1
≤
M
≤
10000
,
1≤M≤10000,
1≤M≤10000,
1
≤
A
i
≤
1000
,
1≤Ai≤1000,
1≤Ai≤1000,
答案保证在 int 范围内。
输入样例:
4 4
1 1 2 2
输出样例:
3
3.1题解
3.2代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10010;
int n,m;
int f[N];
int main()
{
cin >> n >> m;
//第一个初始化为1,其余都为0
f[0] = 1;
for(int i = 0;i < n;i++)
{
int v;
cin >> v;
for(int j = m;j >= v;j--)
f[j] += f[j - v];
}
cout << f[m] << endl;
return 0;
}
4.庆功会
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。
期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
输入格式
第一行二个数
n
n
n,
m
m
m,其中
n
n
n代表希望购买的奖品的种数,
m
m
m表示拨款金额。
接下来 n n n行,每行 3 3 3个数, v v v、 w w w、 s s s,分别表示第 I I I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买 0 0 0件到 s s s件均可)。
输出格式
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
数据范围
n
≤
500
,
m
≤
6000
,
n≤500,m≤6000,
n≤500,m≤6000,
v
≤
100
,
w
≤
1000
,
s
≤
10
v≤100,w≤1000,s≤10
v≤100,w≤1000,s≤10
输入样例:
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
输出样例:
1040
4.1题解
本题为一个朴素版多重背包问题
初始状态:f[0][0]
目标状态:f[n][m]
4.2代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 6010;
int n,m;
int f[N];
int main()
{
cin >> n >> m;
for(int i = 0;i < n;i++)
{
int v,w,s;
cin >> v >> w >> s;
for(int j = m;j >= 0;j--)
for(int k = 0;k <= s && k * v <= j;k++)
f[j] = max(f[j],f[j - k * v] + k * w);
}
cout << f[m] <<endl;
return 0;
}
5.买书
小明手里有n元钱全部用来买书,书的价格为 10 10 10元, 20 20 20元, 50 50 50元, 100 100 100元。
问小明有多少种买书方案?(每种书可购买多本)
输入格式
一个整数
n
n
n,代表总共钱数。
输出格式
一个整数,代表选择方案种数。
数据范围
0
≤
n
≤
1000
0≤n≤1000
0≤n≤1000
输入样例1:
20
输出样例1:
2
输入样例2:
15
输出样例2:
0
输入样例3:
0
输出样例3:
1
5.1题解
5.2代码实现
#include<iostream>
using namespace std;
const int N = 1010;
int v[4] = {10,20,50,100};
int f[N];
int main()
{
int m;
cin >> m;
f[0] = 1;
for(int i = 0;i < 4;i++)
for(int j = 0;j <= m;j++)
if(j >= v[i])
f[j] += f[j - v[i]];
cout << f[m] <<endl;
return 0;
}