上一次,我们讲了多重背包问题,其中讲的天花乱坠,希望所有的读者能够听懂 :》
这次,我们将两种背包问题一起讲了,就是让人烦恼的混合背包问题与二维费用的背包问题。
混合背包问题
经典题型
上次的旅行者,又要开始旅途了!谁知道他这次会去哪里呢?他有N件物品,还有一个容量为V的三级 包,重量w[i],价值c[i],有的医疗箱 物品可以取一次,有的医疗包 物品可以取n[i]次,有的绷带 物品能无限取。同样,让他装下的东西价值最多。所以,让你编个程序帮助他。
天哪!这是坑人吗? 有三大类物品,还混合了在一起!
其实,我们简单思考一下,就可以将三者放到一起。考虑到01背包与多重背包极其相似,而完全背包又正好能够单独放出来,那么,那么…(省下10001个那么),就可以将01背包,完全背包与多重背包分开来就可以了嘛。
所以,01背包可以与多重背包完美重合,而完全背包可以另开一个规划,所以,状态转移方程是相同的: 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))。是不是so easy?
伪代码如下:
for(i=1...N)
if 第i件物品是01背包 或多重背包
for(j=1...n[i]) //最多多少个
for(v=V...w[i])
f[v]=max(f[v],f[v-w[i]]+c[i]);
if 第i件物品是完全背包
for(v=w[i]...V)
f[v]=max(f[v],f[v-w[i]]+c[i])
伪代码出现,真实代码也不远了。稍微一抬指头,便出现了下面的程序:
#include<iostream>
using namespace std;
int f[1000001],n[1000001],w[1000001],c[1000001];
int main()
{
int N,V;
cin>>V>>N;
for(int i=1;i<=N;i++)
cin>>w[i]>>c[i]>>n[i];
for(int i=1;i<=N;i++)
{
if(n[i]!=0)
for(int j=1;j<=n[i];j++) //最多多少个
for(int v=V;v>=w[i];v--)
f[v]=max(f[v],f[v-w[i]]+c[i]);
else
for(int v=w[i];v<=V;v++)
f[v]=max(f[v],f[v-w[i]]+c[i]);
}
int max=-99999999;
for(int i=1;i<=V;i++) max=max<f[i]? f[i]:max;
cout<<max;
return 0;
}
二维费用的背包问题
意义
这次,我们先来说说二维费用的背包问题的意义。二维费用?二维费用就是指的是对于每件物品,具有两种费用;选择这件物品必须同时付出这两种代价;两种代价!你会说,我想不出来有什么生活中的例子有需要两种代价!让我告诉你吧,出题人总有想不完的想法,快赶得上作家了!
看着啦!
常见题型
【例n+4】潜水员
题目:潜水员为了潜水要使用特殊的装备。他有一个带有两种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需的气缸总重的最低限度是多少?
例如:潜水员有5个气缸。每行三个数为:氧、氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2号气缸或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
【输入格式】
第一行有2整数m,n(1<=m<=21,1<=n<=79)。它们表示氧,氮各自需要的量。
第二行为整数k(1<=n<=1000)表示气缸的个数。
此后的k行,每行包括ai,bi,ci(1<=ai<=21,1<=bi<=79,1<=ci<=800)3整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
【输出格式】
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
【输入样例】
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
【输出样例】
249
算法分析
费用增加了一维,那么我们状态也只需要增加一维即可。设f[i][v][u] 表示前i件物品付出的两种代价分别为v和u时可以获得的最大的价值。所以,状态转移方程:
f
(
i
)
(
v
)
(
u
)
=
m
a
x
(
f
(
i
−
1
)
(
v
)
(
u
)
,
f
(
i
−
1
)
(
v
−
a
(
i
)
)
(
u
−
b
(
i
)
)
+
c
(
i
)
)
f(i)(v)(u)=max(f(i-1)(v)(u),f(i-1)(v-a(i))(u-b(i))+c(i))
f(i)(v)(u)=max(f(i−1)(v)(u),f(i−1)(v−a(i))(u−b(i))+c(i))
如果使用之前分析的方法来,可以将数组缩减成二维数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环,多重背包拆分物品。
有时,“二维费用”限制了一个最多能取M件物品。这实际上相当于每件物品多了一个“件数”的费用,费用均为1,可以付出最大数费用为M。
听不懂是吧?
换句话说一说,设f[v][m]表示费用为v,最多选m件时可得到的最大价值,价值则根据物品类型进行不同的方法循环更新,最有在f[0…v][0…m]的范围内寻找答案。
另外,如果是要求恰好取m件物品,就在f[0…v][m]里寻找答案。
直接给代码:
#include<cstdio>
#include<cstring>
using namespace std;
int v, u, k;
int a[1001], b[1001], c[1001];
int f[101][101];
int main()
{
memset(f,127,sizeof(f));
f[0][0] = 0;
scanf("%d%d%d",&v,&u,&k);
for (int i = 1; i <= k; i++)
scanf("%d%d%d",&a[i],&b[i],&c[i]);
for (int i = 1; i <= k; i++)
for (int j = v; j >= 0; j--)
for (int l = u; l >= 0; l--)
{
int t1 = j+ a[i],t2 = l + b[i];
if (t1 > v) t1 = v; //若氮、氧含量超过需求,可直接用需求量代换,
if (t2> u) t2 = u;
if (f[t1][t2] > f[j][l] + c[i]) f[t1][t2] = f[j][l] + c[i];
}
printf("%d",f[v][u]);
return 0;
}
啊哈,今天的文章写完了,真舒服啊!也请大家多多支持,多多指出问题!谢谢!