背包问题
01背包
有$ N $件物品和一个容量是 $V $的背包。每件物品只能使用一次。
第 $i$ 件物品的体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
Tips: 这个是最基础的背包问题。 背包问题属于动态规划的一种, $f[i] $表示体积为 $i $背包的最大的价值。那么转移方程就比较简单了,对于物品(v, w) 有选和不选两种方案。
$ f[i] =max( f[i], f[i-v] +w) $ 这样的式子就是比较nice的选择。 01 背包问题的体积是从大到小进行枚举的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 01 背包问题, f[i] =max(f[i], f[i -w]+ v)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N= 1010;
int f[N];
int v[N], w[N];
int n, m;
int main()
{
cin>> n>> m;
for(int i =0; i< n; i++)
{
int v, w;
cin >> v>> w;
for (int j =m ; j>=v; j--)
f[j] =max(f[j], f[j -v]+w );
}
cout << f[m]<<endl;
return 0;
}
|
使用 n 个数字表示总和为 m 的总共的情况数。 n 表示从 1 到n 的数字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 这个是一个 01 背包问题, dp[i][j] 表示使用前 i 个数字表示总和为 j 的情况
# 对于数字 i 有两种选法:选或者不选
def solution(n, m):
p = [i for i in range(n+1)]
dp = [[0] * (m+1) for _ in range(n +1)]
# 初始化
for i in range(n):
dp[i][0] =1
for i in range(1,m):
dp[0][i] =0
for i in range(1, n+1):
for j in range(m+1):
if p[i] <= j:
dp[i][j] = dp[i-1][j] + dp[i-1][j -p[i]]
else:
dp[i][j] = dp[i-1][j]
return dp[n][m]
|
完全背包问题
有$ N $件物品和一个容量是 $V $的背包。每件物品都有无限件可用。
第 $i$ 件物品的体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
Tips: 这个跟上面的不同点在于,物品是无限件可用,01 背包是只能用一个。从代码实现上看, 01背包问题的 体积是从大到小遍历的,然后完全背包问题是从小到大遍历的。
01 背包问题和 完全背包问题的区别在于前者物品只有选择与否两种方案。而完全背包对于每件物品有无限件可以选择。对于体积v 是从小到大进行枚举的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 转移方程 f[i] =max(f[i], f[i -v]+w) 没有变,实现的时候有差别
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =1010;
int f[N];
int n, m;
int main()
{
cin >> n>>m;
for(int i =0; i< n ;i++)
{
int v, w;
cin >> v>>w;
for(int j =v; j<=m ;j++) // 注意这个是 <= 号; 这个起始点是假设放入了体积为v 的物品
f[j] =max(f[j], f[j -v]+w);
}
cout<< f[m]<<endl;
return 0;
}
|
多重背包问题 1
有$ N $件物品和一个容量是 $V $的背包。每件物品都有无限件可用。
第 $i$ 件物品最多有 $s_i$ 件, 每件体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
$$
\begin{split}
0< & N \leq 100 \\
0< & V \leq 100 \\
0< & v_{i}, w_{i}, s_{i} \leq 100
\end{split}
$$
Tips: 多重背包问题是转换成 01 背包问题进行解决的,这个大的思路是没有问题的。只是对于不同的数据规模有两种不同的实现手段。当数据规模比较小的时候,三重循环就搞定了;当数据规模比较大的时候,需要使用一个结构体进行操作。
(多重背包问题对物品的个数进行了限制,不是无限个,转换成01 背包问题,体积是从大到小进行枚举)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =110;
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 =1; k<=s && k*v <=j ; k++)
f[j] =max(f[j], f[j -k*v] +k*w);
}
cout <<f[m]<< endl;
return 0;
}
|
多重背包问题 2
有$ N $件物品和一个容量是 $V $的背包。每件物品都有无限件可用。
第 $i$ 件物品最多有 $s_i$ 件, 每件体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
数据范围:
$$
\begin{split}
0< & N \leq 1000 \\
0< & V \leq 2000 \\
0< & v_{i}, w_{i}, s_{i} \leq 2000
\end{split}
$$
Tips: 主要区别在于数据的维度。 这个是转换成 成 01 背包问题,上一道题目是在 01 背包问题的基础上(框架上)进行修改。 这个从时间复杂度上的优化:$1000 \times 2000 \times 2000$ -> $1000 \times 2000 \times \log(2000) $ 。经过优化 基本上在 $ 10^7 $,所以这个速度是可以接受的。 c++ 在 1s 中就是可以计算 $10^7 $ 这样运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N =2010 ; // 这个数量是 v的最大值
int f[N];
int n, m;
struct Good{
int v, w;
};
vector<Good> goods;
int main()
{
cin >> n>>m;
for(int i =0; i<n ; i++)
{
int v, w, s;
cin >> v>> w>>s;
for (int k =1; k<=s; k *=2)
{
s -=k;
goods.push_back({v*k, w*k});
}
if(s >0) goods.push_back({v*s, w*s});
}
// 01 hnapsack
// goods 的长度不一定是 n, 所以不能这样进行遍历
/*
for(int i =0; i<n; i++)// 这个
for(int j =m ; j>= goods[i].v; j--)
{
f[j] =max(f[j], f[j -goods[i].v] +goods[i].w);
}
*/
for(auto good : goods)
for(int j =m ; j>= good.v; j--)
f[j] =max(f[j], f[j -good.v]+ good.w);
cout << f[m]<< endl;
return 0;
}
|
混合背包问题
有 $ N$种物品和一个容量是 $V$的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 $s_i $次(多重背包);
每种体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
Tips: 分类讨论,多重背包问题转成 01背包问题,所以最后是有两类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
/*
分成两类: 01 背包问题和完全背包问题;对于多重背包问题使用 二进制进行优化,得到01 背包问题。
所以需要一个新的结构体
01 背包问题只有两个循环,一个是物品的循环,一个是商品的循环
*/
const int N =1010;
int f[N];
int n, m;
struct Thing
{
int v, w, s;
};
vector<Thing> things;
int main()
{
cin >>n>>m;
for(int i =0; i<n ;i++)
{
int v, w, s;
cin >> v>> w>>s;
if(s ==-1) things.push_back({v, w, -1});
else if (s ==0) things.push_back({v, w, 0});
else
{
for(int k =1; k<=s; k *=2)
{
s -=k;
things.push_back({v*k, w*k, -1});
}
if (s >0) things.push_back({v*s, w*s, -1});
}
}
// 01 背包问题
for(auto thing: things)
{
if(thing.s ==-1)
{
for( int j =m ; j>= thing.v; j--)
f[j] =max(f[j], f[j -thing.v]+ thing.w);
}
else
{
for(int j =thing.v ; j<= m; j++)
f[j] =max(f[j], f[j -thing.v] +thing.w);
}
}
cout << f[m]<<endl;
return 0;
}
|
二维费用的背包问题
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是$ v_i$, 重量是 $m_i$,价值是$ w_i$。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
Tips: 一般来说背包问题只有一个限制条件:V。但是这种是可以扩展到多维,即有多个限制条件,比如重量。很简单的扩展是加上一个循环。同理 f 这样的函数是可以扩展成二维的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =1010;
int f[N][N];
int n,v, m;
int main()
{
cin >> n>> v>>m;
for (int i =0 ; i< n; i++)
{
int a, b, c;
cin >> a>> b>>c;
for(int j =v ;j>=a ; j--)
for(int k =m ; k>= b; k--)
{
f[j][k] =max(f[j][k], f[j- a][k -b] +c);
}
}
cout << f[v][m]<< endl;
return 0;
}
|