1. P1060 开心的金明
解题思路
最基本的01背包问题,如果不用优化成一维数组,那么用f(i,j)表示前i个商品花j元钱获得的最大价值。按第i个商品放与不放可以分为:
f[i,j]=max(f[i-1,j],f[i-1,j-v[i]]+v[i]*w[i])
#include <iostream>
#include<cstdio>
using namespace std;
int f[30][30005];//总的商品个数和总的钱数
int v[30],w[30];
//f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+v[i]*w[i]);
int main(int argc, char** argv) {
int N,m;
scanf("%d %d",&N,&m);
for(int i=1;i<=m;i++) scanf("%d %d",&v[i],&w[i]);
for(int i=1;i<=m;i++){
for(int j=0;j<=N;j++) f[i][j]=f[i-1][j];//先更新,再判断
for(int j=v[i];j<=N;j++){
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+v[i]*w[i]);
}
}
printf("%d\n",f[m][N]);
return 0;
}
优化成一维数组可以表示成:其中
j
j
j表示总的钱,
i
i
i表示第
i
i
i个商品,
f
[
j
]
f[j]
f[j]表示用金额为
j
j
j的钱获得目标的最高价值。
f
[
j
]
=
m
a
x
(
f
[
j
]
,
f
[
j
−
a
[
i
]
.
v
]
+
a
[
i
]
.
v
∗
a
[
i
]
.
p
)
f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p)
f[j]=max(f[j],f[j−a[i].v]+a[i].v∗a[i].p)
#include<iostream>
#include<cstdio>
using namespace std;
//f[j]=max(
struct node{
int v,p;
}a[30];
int f[30100];
int main(int argc, char** argv) {
int N,m;
scanf("%d%d",&N,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&a[i].v,&a[i].p);
}
for(int i=1;i<=m;i++){
for(int j=N;j>=a[i].v;j--){
f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p);
}
}
printf("%d\n",f[N]);
return 0;
}
2. P1164 小A点菜
解题思路
参考自:https://www.luogu.org/blog/user31798/solution-p1164
与1有点变化,这里的背包要求更加严格,即必须装满整个背包,不能有空余的空间。这里利用f[i][j]表示前i个商品刚好花j元钱所得到的总的方案数。
5 4 //总的商品个数和总的钱数
1 1 2 2 4//每个商品需要花的钱
- 如果每个商品需要花的钱分别为1,1,2,2,4,那么显然有f[5][4]=f[4][4]+1,因为最后一个商品和总的钱数相等,买前5个商品的总的方案数等于买前四个商品的方案数加一。即如果当j==a[i]时,有:f[i][j]=f[i][j-1]+1;
- 如果每个商品需要花的钱分别为1,1,2,2,5,那么有f[5][4]=f[4][4],因为最后一个商品的价格比总价还高,不可能与前面的商品组合成新的方案数。即如果j>a[i]时,f[i][j]=f[i-1][j];
- 如果每个商品的花费分别为1,1,2,2,3,最后一个商品可以和前面的商品组合成更多的商品数,有f[5][4]=f[4][4]+f[4][4-3]=f[4][4]+f[4][1],即如果a[i]<j,那么a[i]可以和前面的商品组成更多的组合数,f[i][j]=f[i-1][j]+f[i-1][j-a[i]]。
所以递推关系可以表示成:
当j==a[i]时:f[i][j]=f[i-1][j]+1
当j>a[i]时,f[i][j]=f[i-1][j]+f[i-1][j-a[i]]
当j<a[i]时,f[i][j]=f[i-1][j]
#include<iostream>
#include<cstdio>
using namespace std;
int a[110],f[110][10010]={0};
int main(int argc, char** argv) {
int n,m,ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j==a[i]) f[i][j]=f[i-1][j]+1;//==
if(j>a[i]) f[i][j]=f[i-1][j]+f[i-1][j-a[i]];
if(j<a[i]) f[i][j]=f[i-1][j];
}
}
printf("%d\n",f[n][m]);
return 0;
}
3. P1064 金明的预算方案
解题思路
参考自https://www.luogu.org/blog/user20197/solution-p1064
分层dp,有依赖性的背包问题,注意到这里的附件必须与主件结合才能使用。首先直接对所有主件按一般背包问题进行处理;然后对于特定的主件引入附件再采用背包的思想进行处理,若引入附件的结果优于未引入主件的背包问题,则更新最优的结果。
#include<iostream>
#include<cstdio>
using namespace std;
struct node{
int v,p,q;
}a[65],b[65][1000]; //a记录所有信息,b记录附件信息
int N,m;
int f[32100];
int index[1000];
int main(int argc, char** argv) {
scanf("%d%d",&N,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a[i].v,&a[i].p,&a[i].q);
if(a[i].q!=0){//附件
index[a[i].q]++;
b[a[i].q][index[a[i].q]].v=a[i].v;
b[a[i].q][index[a[i].q]].p=a[i].p;
}
}
for(int i=1;i<=m;i++){
for(int j=N;a[i].q==0&&j>=a[i].v;j--){
f[j]=max(f[j],f[j-a[i].v]+a[i].v*a[i].p);//主件
if(j>=a[i].v+b[i][1].v)//附件1
f[j]=max(f[j],f[j-a[i].v-b[i][1].v]+a[i].v*a[i].p+b[i][1].v*b[i][1].p);
if(j>=a[i].v+b[i][2].v)//附件2
f[j]=max(f[j],f[j-a[i].v-b[i][2].v]+a[i].v*a[i].p+b[i][2].v*b[i][2].p);
if(j>=a[i].v+b[i][1].v+b[i][2].v)//附件1,2
f[j]=max(f[j],f[j-a[i].v-b[i][1].v-b[i][2].v]+a[i].v*a[i].p+b[i][1].v*b[i][1].p+b[i][2].v*b[i][2].p);
}
}
printf("%d\n",f[N]);
return 0;
}
4. P1616 疯狂的采药
解题思路
完全背包问题,因为每个商品的数量理论上来说是无限的。这时候如果直接转成01背包问题也可以完成。如总的时间为
T
T
T,这时候每个商品的数量可化为有限的
T
a
[
i
]
.
t
\frac{T}{a[i].t}
a[i].tT,然后再对所有商品利用01背包的思想来解决,时间复杂度为
O
(
M
∑
i
=
1
M
T
a
[
i
]
.
t
)
O( M\sum_{i=1}^{M} \frac{T}{a[i].t})
O(M∑i=1Ma[i].tT)。
如果换一种思路不将每个商品的具体数量求出来,则需要对
f
[
j
]
f[j]
f[j]更新时的顺序进行调整。之前在更新
f
[
j
]
f[j]
f[j]时,对于每个
j
j
j从总的时间
T
T
T往回更新,这是为了保证在更新二维情况下的
f
[
i
]
[
j
]
f[i][j]
f[i][j]时,之前的
f
[
i
]
[
j
−
a
[
i
]
.
v
]
f[i][j-a[i].v]
f[i][j−a[i].v]未做改变,即保证第
i
i
i个商品只有两种状态,要么被选,要么不被选。但是现在的第
i
i
i个商品的数量是无限的,所以在更新
f
[
i
]
[
j
]
f[i][j]
f[i][j]时,应该保证之前的
f
[
i
]
[
j
−
a
[
i
]
.
v
]
f[i][j-a[i].v]
f[i][j−a[i].v]做了改变,即第
i
i
i个商品可以多次被选。
所以将原来从总的时间
T
T
T往回更新,换成
a
[
i
]
.
v
a[i].v
a[i].v往
T
T
T顺序更新即可。时间复杂度为
O
(
M
T
)
O(MT)
O(MT)。
#include <iostream>
#include<cstdio>
using namespace std;
struct node{
int t,v;
}a[10100];
int f[100010];//M,T
int main(int argc, char** argv) {
int T,M;
scanf("%d%d",&T,&M);
for(int i=1;i<=M;i++) scanf("%d%d",&a[i].t,&a[i].v);
for(int i=1;i<=M;i++){
for(int j=a[i].t;j<=T;j++){//顺序更新
f[j]=max(f[j],f[j-a[i].t]+a[i].v);
}
}
printf("%d\n",f[T]);
return 0;
}
5. P1156 垃圾陷阱
解题思路:
非常有意思的一道题,是01背包的变种(加强版),对于题目中的每个垃圾分为吃或不吃两种状态,设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示到第
i
i
i个垃圾达到高度为
j
j
j时剩下的时间。
(1)吃:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
j
]
+
a
[
i
]
.
F
−
(
a
[
i
]
.
T
−
a
[
i
−
1
]
.
T
)
)
f[i][j]=max(f[i][j],f[i-1][j]+a[i].F-(a[i].T-a[i-1].T))
f[i][j]=max(f[i][j],f[i−1][j]+a[i].F−(a[i].T−a[i−1].T))
(2)不吃:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
]
,
f
[
i
]
[
j
−
a
[
i
]
.
H
]
−
(
a
[
i
]
.
T
−
a
[
i
−
1
]
.
T
)
)
f[i][j]=max(f[i][j],f[i][j-a[i].H]-(a[i].T-a[i-1].T))
f[i][j]=max(f[i][j],f[i][j−a[i].H]−(a[i].T−a[i−1].T))
如果最后
j
j
j转化不到总高度
D
D
D,那么存活的最长时间=
m
a
x
(
f
[
i
]
[
j
]
+
a
[
i
]
.
T
)
max(f[i][j]+a[i].T)
max(f[i][j]+a[i].T),即当前时间
a
[
i
]
.
T
a[i].T
a[i].T加上当前剩余的时间
f
[
i
]
[
j
]
f[i][j]
f[i][j]。
最后注意按垃圾出现的时间升序排列。
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define minv -233233233
struct node{
int t,f,h;
bool operator < (const node &b) {
return t<b.t;//时间升序;
}
}a[110];
int d,g;
int f[110][110];//i商品,j高度,f[i][j]剩下的生命时间
int main(int argc, char** argv) {
scanf("%d%d",&d,&g);
for(int i=1;i<=g;i++){
scanf("%d%d%d",&a[i].t,&a[i].f,&a[i].h);
}
sort(a+1,a+g+1);
for(int i=1;i<=110;i++) for(int j=0;j<=110;j++) f[i][j]=minv;
f[0][0]=10; int flag=0;
a[0].f=0;a[0].h=0;a[0].t=0;
for(int i=1;i<=g;i++){
for(int j=0;j<=d;j++){
if(f[i-1][j]-(a[i].t-a[i-1].t)>=0)
f[i][j]=max(f[i][j],f[i-1][j]+a[i].f-(a[i].t-a[i-1].t));//吃
if(j-a[i].h>=0&&f[i-1][j-a[i].h]-(a[i].t-a[i-1].t)>=0){
f[i][j]=max(f[i][j],f[i-1][j-a[i].h]-(a[i].t-a[i-1].t));//不吃
if(j==d){//如果能逃出来,一定是由其他状态转化而来
flag=1;
printf("%d\n",a[i].t);
return 0;
}
}
}
}
if(!flag){
int ans=-1;
for(int i=1;i<=g;i++){
for(int j=0;j<=d;j++){
if(f[i][j]!=minv){
ans=max(ans,f[i][j]+a[i].t);
}
}
}
printf("%d\n",ans);
}
return 0;
}