补上我的上篇讲解01背包与完全背包,这一篇讲解两个我认为比较经典的背包题。一个是多重背包题,直接套模板可以ac,一个是多重背包与完全背包题,需要改变一下模板。当然只要理解了背包的核心就能轻松改动模板。道可道,非常道,这个核心我还是模糊到难以讲出来。
多重背包问题—Coin HDU - 2844
简单说一下题目:
有件商品最大价格不超过m, 然后你有很多种价值不同的硬币,且数目有限。题目问你,在1到m中的那些价格中,求手中的硬币刚好能够支付的价格(硬币价值只有刚好等于某个价格时候才行,超过了就不满足题意了)。
题目思路:
如果对多重背包了解的,可以很快想出来该题的解题算法,就是一个标准的套模板题嘛。我们看看一般的多重背包题意。
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值 是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
细细品读后,发现这个跟本题的意思基本一样。多重背包问题,一个有限容量的背包要装下最大价值的物品,眼前由很多种物品,有一定的价值和体积,但是呢每种物品数量又是有限的。这个跟完全背包有点不同,完全背包是同种物品数量无限
说到这,可以知道这是一个简单的多重背包题了吧,多重背包由基础背包与完全背包组成,一个模板就能解决这一问题。当然死记模板也是不好。
下面看看代吧。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
//把一种物品的花费cost,价值weight,数目amount放在一个结构体里绑定起来,方便调用
//当然使用数组也行,这一题,用不到cost的值,不写也行。
struct Pack
{
int cost;
int weight;
int amount;
}pack[maxn];
//状态转移的关键数组f[],和背包的总容量V(也就是本题的最大价值)
int f[maxn], V;
//基础背包
void Basepack(int cost, int weight)
{
for(int v=V; v>=cost; v--)
f[v] = max(f[v], f[v-cost]+weight);
}
//完全背包
void Competepack(int cost, int weight)
{
for(int v=cost; v<=V; v++)
f[v] = max(f[v], f[v-cost]+weight);
}
//多重背包,前两个背包的组合
void Multiplepack(int cost, int weight, int amount)
{
//当某一种物品的总消耗体积大于背包的总容量,可以间接地视作是完全背包问题
//你可以把它当作是,这种物品是无限数目的。
if(cost*amount >= V)
{
Competepack(cost, weight);
return ;
}
//不是完全背包就是基础背包了
for(int k=1; k<amount; k<<=1)
{
Basepack(k*cost, k*weight);
amount -= k;
}
Basepack(amount*cost, amount*weight);
}
int main()
{
//是消除c++的输入输出的不必要的操作,减少时间,感觉没啥用,有的时候用c++输出照样超时。
ios::sync_with_stdio(false);
int n;
while(cin>>n>>V && n+V)
{
for(int i=0; i<n; i++) cin>>pack[i].weight;
for(int i=0; i<n; i++) cin>>pack[i].amount;
//对f数组初始化,可以直接用memset
for(int i=0; i<=V; i++) f[i] = 0;
//这里要看清题意,物品的消耗是价值,而求的也是价值,于是cost的位置被weight占了
for(int i=0; i<n; i++)
Multiplepack(pack[i].weight, pack[i].weight, pack[i].amount);
//求1toV中有哪些价格刚好支付得了。
int ans = 0;
for(int i=1; i<=V; i++)
if(f[i] == i) ans++;
cout<<ans<<endl;
}
return 0;
}
混合背包问题——The trouble of Xiaoqian HDU - 3591
这个输入输出格式就不多说了,自己找链接看吧。
思路
细看题发现似乎是背包题,而且是多重背包题,但是又发现有一个收银员很碍事。这个其实就是混合背包问题。一个题目里面有两个背包问题。
首先是顾客的硬币数做一个背包问题,是多重背包。然后收银员找的硬币数,因硬币数无限,看作是完全背包问题。
依据题意定义子问题f1[i]表示支付i价值的硬币的最小数量,而f2[i]表示找i价值的硬币的最小数量。
那么怎么求最小流动的硬币数量。
当支付的硬币总价值为k,k>=V。那么f1[k]就是支付的最小硬币数量,那么找的硬币的最小数量就是f2[k-V].
讲解可能不清楚,那就看看下面的代码自己体会一下吧。
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 20010
struct Pack
{
int cost;
int weight;
int amount;
}pack[maxn];
//顾客的硬币数状态f1, 收银员找硬币数目的状态
int f1[maxn], f2[maxn], V;
//因为题目求的是最小的硬币数目,那么就得稍微修改一下摸板,f[]不再是求最大价值了,而是求最小硬币数目
//max 变为 min ,然后是相加硬币数目+amount 而不是加价值weight了,故而得额外加一个参数
//这里最大范围不在局限于V了,因为价格可以超出,我们把范围就定为比较大的maxn。
void Basepack(int cost, int weight, int amount, int* f)
{
for(int v=maxn; v>=cost; v--)
f[v] = min(f[v], f[v-cost]+amount);
}
//完全背包,比较的时候每次只加1个硬币数,主要是完全背包是由上一个状态推下一状态,
//上一个状态是k个硬币数,那么下一个状态是k+1个数目了。弄懂二维转化为一维数组就能容易明白这个道理了。
void Competepack(int cost, int weight, int* f)
{
for(int v=cost; v<=maxn; v++)
f[v] = min(f[v], f[v-cost]+1);
}
void Multiplepack(int cost, int weight, int amount ,int* f)
{
if(cost*amount >= V)
{
Competepack(cost, weight, f);
return ;
}
for(int k=1; k<amount; k<<=1)
{
Basepack(k*cost, k*weight, k, f);
amount -= k;
}
Basepack(amount*cost, amount*weight, amount, f);
}
int main()
{
int n, cas = 1;
while(cin>>n>>V && n+V)
{
for(int i=0; i<n; i++) cin>>pack[i].weight;
for(int i=0; i<n; i++) cin>>pack[i].amount;
//求最小状态,初始化为无穷大,自定义的无穷大INF
memset(f1, INF, sizeof(f1));
memset(f2, INF, sizeof(f2));
f1[0] = f2[0] = 0;
//对顾客支付的硬币,多重背包处理
for(int i=0; i<n; i++) Multiplepack(pack[i].weight, pack[i].weight, pack[i].amount, f1);
//对于收银员的硬币, 完全背包处理
for(int i=0; i<n; i++) Competepack(pack[i].weight, pack[i].weight, f2);
//找到最小的流动硬币数量
int ans = INF;
for(int i=V; i<=maxn; i++)
ans = min(ans, f1[i]+f2[i-V]);
if(ans == INF)ans = -1;
printf("Case %d: %d\n", cas++, ans);
}
return 0;
}
经典的列题就讲这四个吧,理解后能更深刻地了解三个背包了,也能更灵活地使用模板了。