BB in Start
你们过你们的五一,我写我的算法课堂
本章知识点:
- 01背包
- 完全背包
- 多重背包
- 部分背包
阅读本文,你需要:
- 有编程基础
- 了解过DP
- 有学过排序算法,了解一部分关于矩阵
- 了解过一些数据结构
参考资料
开始BB
今天,小L又在玩我的世界了
这次,小L面临着一个巨大的问题:
众所周知(你不知道现在也知道了),MC中,只有玩家的背包、潜影贝盒可以移动储存。而箱子、末影箱则是固定储存
小L看到了一些钻石(哇塞!),他想挖走这些钻石。
但是很无奈,他的背包满了(悲催)
所以,他必须丢掉一些东西!
他把背包里的每一个东西都拿出来,翻来翻去的比较着
“泥土、石头可以搭桥”
“树苗可以长成树”
“剑可以打怪物”
……
好难选择啊!每个物品都有价值,都不想扔,但必须要扔!怎么办啊……
停!打住!
这就是一个背包问题。
背包问题给出了一序对集合
R
n
=
{
t
i
=
(
w
i
,
v
i
)
∣
i
=
1
,
2
,
,
,
n
}
Rn=\{ti=(wi,vi)∣i=1,2,,,n\}
Rn={ti=(wi,vi)∣i=1,2,,,n} ,其中
w
i
w_i
wi 是物品总重量,
v
i
v_i
vi 是物品价值,要求从中选取总重量不超过
W
W
W 的物品,使得物品总价值
V
V
V 尽可能的大。
简单点说就是:
有很多吃的,但是你只能吃一部分,问你怎么让吃数量的最多(抱歉我是个吃货)
当然,在以上公式的条件中做文章,又可以派生出很多的问题。
我们先来看看最简单的一个——01背包
01背包——选,或者不选
百度说,01背包就是
有
N
N
N件物品和一个容量为
V
V
V的背包。第
i
i
i件物品的价格(即体积,下同)是
w
[
i
]
w[i]
w[i],价值是
c
[
i
]
c[i]
c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
那么,对于物品来说,只有两种选择:
选,或者不选
这就是为什么这个问题的名字叫做01(0是不选,1是选)
首先告诉你,解决背包问题的经典思想是著名的DP
那么,开始找方程啦~
如果我们用
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个背包装入容量为
v
v
v的背包中所可以获得的最大价值。
那么,可以得出方程:
f
[
i
]
[
v
]
=
m
a
x
(
f
[
i
−
1
]
[
v
]
,
f
[
i
−
1
]
[
v
−
w
[
i
]
]
+
c
[
i
]
)
f[i][v] = max(f[i-1][v], f[i-1][v-w[i]]+c[i])
f[i][v]=max(f[i−1][v],f[i−1][v−w[i]]+c[i])
结束!感谢大家收看……
说实话01背包到这里其实差不多就算结束了,我们来看看有关01背包的题目
采药
题目描述 Description
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入描述 Input Description
输入第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。输出描述 Output Description 输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例输入 Sample Input
70 3
71 100
69 1
1 2样例输出 Sample Output
3
数据范围及提示 Data Size & Hint 【数据规模】
对于30%的数据,M<=10;
对于全部的数据,M<=100。
好我们翻译一下题目:
采每一株都需要一些时间,每一株也有它自身的价值,你应该可以让采到的草药的总价值最大
那么,时间看成重量,价值不变,裸题一个,咱们A去!
来人,上代码~
#include<stdio.h>
#include<algorithm>
using namespace std;
int f[1001][1001];
int main(){
int T, n,c[10001], v[10001];
scanf("%d%d", &T, &n);
for(int i = 1; i <= n; i++)
scanf("%d%d", &v[i], &c[i]);
for(int i = 1; i <= n; i++){
for(int j = 0; j <= T; j++)
f[i][j] = f[i-1][j];
for(int j = 0; j+v[i] <= T; j++)
f[i][j] = max(f[i][j] + c[i], f[i-1][j+v[i]]);
}
int ans = 0;
for(int i = 0; i <= T; i++)
ans = max(ans, f[n][i]);
printf("%d", ans);
return 0;
}
当然,还可以用一维数组这样写
设
f
[
v
]
f[v]
f[v]表示重量不超过v公斤的最大价值, 则
f
[
v
]
=
m
a
x
(
f
[
v
]
,
f
[
v
−
w
[
i
]
]
+
c
[
i
]
)
,
f[v]=max(f[v],f[v-w[i]]+c[i]) ,
f[v]=max(f[v],f[v−w[i]]+c[i]),当
v
>
=
w
[
i
]
,
1
<
=
i
<
=
n
v>=w[i],1<=i<=n
v>=w[i],1<=i<=n。
那么:
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxm = 2001, maxn = 101;
int m, n;
int w[maxn], c[maxn];
int f[maxm];
int main()
{
scanf("%d%d",&m, &n); //背包容量m和物品数量n
for (int i=1; i <= n; i++)
scanf("%d%d",&w[i],&c[i]); //每个物品的重量和价值
for (int i=1; i <= n; i++) //设f(v)表示重量不超过v公斤的最大价值
for (int v = m; v >= w[i]; v--) //注意是逆序
f[v] = max(f[v-w[i]]+c[i], f[v]);
printf("%d\n",f[m]); // f(m)为最优解
return 0;
}
完全背包——选或不选,选几个?
完全背包和01背包很相似,只不过,完全背包加了一个条件——选几个?
那我们可以这么描述:
有
N
N
N种物品和一个容量为
V
V
V的背包,每种物品都有无限件可用。第
i
i
i种物品的费用是
w
[
i
]
w[i]
w[i],价值是
c
[
i
]
c[i]
c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
和01背包一样,我们可以写出状态转移方程:
f
[
i
]
[
v
]
=
m
a
x
(
f
[
i
−
1
]
[
v
−
k
∗
c
[
i
]
]
+
k
∗
w
[
i
]
∣
0
<
=
k
∗
c
[
i
]
<
=
v
)
f[i][v]=max(f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v)
f[i][v]=max(f[i−1][v−k∗c[i]]+k∗w[i]∣0<=k∗c[i]<=v)
此时,某dalao走来说:
当一个物品的价值小于另一个物品的价值,但是价格高于另一个物品,我们就可以不去考虑这个物品。即若两件物品
i
、
j
i、j
i、j满足
c
[
i
]
<
=
c
[
j
]
且
w
[
i
]
>
=
w
[
j
]
c[i]<=c[j]且w[i]>=w[j]
c[i]<=c[j]且w[i]>=w[j]则将物品
j
j
j去掉,不用考虑。我们为什么要买一个又贵又难吃的东西呢(╯▽╰)
哦好有道理诶!
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxm=2001,maxn=101;
int n,m,v,i;
int c[maxn],w[maxn];
int f[maxm];
int main()
{
scanf("%d%d",&m,&n); //背包容量m和物品数量n
for(i=1;i<=n;i++)
scanf("%d%d",&w[i],&c[i]);
for(i=1;i<=n;i++)
for(v=w[i]; v<=m; v++) //设 f[v]表示重量不超过v公斤的最大价值
//这里是v++ 顺序 区别于01背包
f[v]=max(f[v-w[i]]+c[i], f[v]);
printf("%d\n", f[m]); // f[m]为最优解
return 0;
}