您的位置:首页 > 其它

背包那些经典的例子( 上 )

2017-07-21 00:01 148 查看
发现自己对背包问题还是情有独钟的嘛, 毕竟它也算的上是我真正学到的第一个算法。哎,现在我还是一个苦命挣扎的菜鸟。废话也不多说了,下面进入正题吧,这些都是菜鸟在刷背包题遇到的基础问题,作为菜鸟的我就是刷这些题一步一步理解背包的。


01背包问题——Bone Collector HDU - 2602

(我喜欢叫它基础背包,下面我都用基础背包叫它了)。

就不复制英文题目了,简单说一下意思,就是有个怪人,很喜欢收集骨头,然后有一堆不同的骨头,每个骨头有不同的价值和体积,你得用一个容量为V的背包尽可能地去装满一袋最大价值的骨头。

输入:

有T个案列,输入骨头数N,和袋子的容量V(N<=1000,V<=1000),然后输入一行骨头价值,一行体积。

至于思路问题好像真没什么说的,感觉就是套模板。

这个可以简化成基础背包问题,问题描述是这样的:

有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。


然后对比一下,发现其实就是名词变了而已,那么直接写出模板来。它的状态转移公式为f[i][j] = max( f[i-1][j], f[i-1][j-w[i]] + v[i] )。

对于初学的来说,这个还真难理解,作为菜鸟的我也花了两三天才真正弄懂这公式。不解的可以看看我的另一篇博客对基础背包的公式理解,不过这篇是当时理解灵感来的时候写好的,感觉没有写好,不懂再去翻翻大佬的博客去吧,毕竟我也是个菜鸟╮( •́ ₃•̀ )╭

#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
//用结构体为了绑定每一件物品的价值与体积,直接用两个数组也可以。
struct Pack
{
int cost;
int val;
};
//f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。 用于状态转移的重要函数。
int f[maxn][maxn];
int main()
{
int t, n, m;
pack a[maxn];
cin>>t;
while(t--)
{
memset(f, 0, sizeof(f));
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i].val;
for(int i=1; i<=n;i++)
cin>>a[i].cost;

//两个循环可能看得有点蒙蔽,不过一般用到二维数组都是要两个循环的嘛,第一个是遍历每件物品用的,第二个j循环是表示当前物品放入一个j容量的背包。符合f[][]子问题定义嘛。
for(int i=1; i<=n; i++)
for(int j=0; j<=m;j++)
{
//有j-a[i].cost在,故而要判断一下,这个也可以不用判断,把j的范围改一下就可以,自己想一下吧。
if(j >= a[i].cost) f[i][j] = max(f[i-1][j], f[i-1][j-a[i].cost]+a[i].val);
else f[i][j] = f[i-1][j];
}
//跟据子问题定义的f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
就能推出最大价值的状态为f[n][m],理解很容易,替换一下子母i,v就行了。
cout<<f
[m]<<endl;
}
return 0;
}


这只是一个最简单的,为了优化空间,我们得用到一维数组来写代码。

这个是基础背包模板代码:

#include <bits/stdc++.h>
using namespace std;
#define maxn 1010
struct Pack
{
int cost;
int weight;
}a[maxn];
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);
}
int main()
{
int t, n;
cin>>t;
while(t--)
{
cin>>n>>V;
for(int i=1; i<=n; i++) cin>>a[i].weight;
for(int i=1; i<=n; i++) cin>>a[i].cost;

memset(f, 0, sizeof(f));
for(int i=1; i<=n; i++)
Basepack(a[i].cost, a[i].weight);
//f[i]代表着把东西装入容量为i的背包里的最大价值,然后自然就知道输出f[V]咯。
cout<<f[V]<<endl;
}
return 0;
}


好了这是最基础的基础背包,下面就开始讲解完全背包吧。

完全背包问题——Piggy-Bank HDU - 1114

不想浪费我的时间与博客空间了,就直接写输入格式了,其他自己点开链接翻译吧。

输入:

有T个案列,钱罐的开始与最后的重量为E和F,下面有n中硬币,可以无限取,n行是硬币的价值与重量。

求钱罐里最少数目的钱。

我的代码直接用一维数组了,就是个完全背包的模板,没怎么去看二维的代码,因为用到三个循环真是太麻烦了!

基础背包公式:f[i][j] = max( f[i-1][j], f[i-1][j-w[i]] + v[i] )。

完全背包公式:f[i][j] = max(f[i-1][j], f[i - 1][j - k*w[i] ] + k*v[i] );

很类似吧!因为物品无限嘛,得有个k值一个一个取同种物品。

二维转一维同基础背包的一维公式一样

f[v] = max(f[v], f[v-cost]+weight)

数学思维看问题,不多说了,哎,真是恐怖的数学思维!

来吧看模板代码吧!

#include <bits/stdc++.h>
using namespace std;
#define maxn 10010
//求最小值,则f的初始化不能为0了,而是为无穷的,当然程序里是没有无穷大的,就定一个很大的数了。
#define INF 0x3f3f3f3f
struct Pack
{
int cost;
int weight;
}p[maxn];
int f[maxn], V;
//与上一个基础背包的模板很类似,因为完全背包每种物品无限个,于是乎是从一个开始取该物品到k个为止,然后呢也就是用它自身的前状态推出后一个状态,所以v是顺循环的。唔我的博客还没有这段讲解,以后补上吧。
void Competepack(int cost, int weight)
{
for(int v=cost; v<=V; v++)
f[v] = min(f[v], f[v-cost]+weight);
}
int main()
{
int t, E, F, n;
cin>>t;
while(t--)
{
cin>>E>>F;
//V相当于是背包的总容量
V = F-E;
memset(f, INF, sizeof(f));
//动态规划的初始化很微妙,稍微不注意就可能出错!f还得有一个为0,不然可以看
f[0] =
a7fb
0;
cin>>n;
for(int i=1; i<=n; i++)
{
//很巧妙的输入操作,每次遍历都是访问i=1的状态值,不会影响i=2或则i=-1所以就直接在此处输入也行,因为他只会访问a[i]的值,但不访问a[i+1]或者其他的值。
cin>>p[i].weight>>p[i].cost;
Competepack(p[i].cost, p[i].weight);
}
//发现当容量为V的最小价值为INF呵呵呵,就不行了,INF定义为无穷大嘛,都无穷大的价值了,这不可能是最小价值的,所以是没有成立的条件了。
if(f[V] == INF)cout<<"This is impossible."<<endl;
else cout<<"The minimum amount of money in the piggy-bank is "<<f[V]<<"."<<endl;

}
return 0;
}


完全背包这里还是没讲出感觉,哎不管了就这样吧,不懂的就敲几遍代码尝试理解吧!《背包九讲》真心不错,有空就看看,感觉为它打了很多次广告。。。。。。

博客写到这里太长了,就分两篇写吧,谔谔,也是算是偷懒了,下次把后面的补上,下一篇我来讲解多重背包 和 多重背包与完全背包的混合使用的两个简单而经典的题目。

哎,越到后面的背包,我讲得越简单模糊,毕竟是入门菜鸟初学没多久,望各位别介意哈......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  acm 背包算法