01背包总结+传授个人经验
写在前面:
自从学了背包这个专题,感觉永远也写不完,之前写了三种类型的01背包,结果现在发现都是简单的01背包、后来又学了多重
背包、完全背包,其中多重背包还可以进行二进制优化。
本文将对这几种常见的背包进行解释
模块一:简单01背包
首先,如果你不知道什么是01背包,请先参考博客,补充相关知识
https://blog.csdn.net/xp731574722/article/details/70766804
那么,对于简单01背包,一共有三种写法,dp的分别有两种,dfs+剪枝的一种,分别对应不同的题目。
①普通写法,也是最简单的写法:
例题是HDU 2602
Bone Collector
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 14336 Accepted Submission(s): 5688
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
Source
HDU 1st “Vegetable-Birds Cup” Programming Open Contest
如果是才接触背包,请认真写题,然后理解
复杂度是 O(n*vol )n是物品个数,vol是背包容量。
第一种写法适用于n和vol都一般大,差不多在1000,两者的乘积在可承受范围内(1e7)。
普通的我就不详细解释了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,vol;
struct spe
{
int value,vol;
};
spe p[1005];
int dp[1005];
int main()
{
int T;
scanf("%d",&T);
while( T-- )
{
scanf("%d %d",&n,&vol);
for( int i = 1 ; i <= n; i++ )
{
scanf("%d",&p[i].value);
}
for( int i = 1 ; i <= n; i++ )
{
scanf("%d",&p[i].vol);
}
memset(dp,0,sizeof dp);
for( int i = 1 ; i <= n ; i++ )
{
for( int j = vol ; j >= p[i].vol ; j-- )
{
dp[j] = max( dp[j],dp[j-p[i].vol]+p[i].value );
}
}
cout<<dp[vol]<<endl;
}
return 0;
}
/*
1 2
1 7
1 3
1 4
1 9
3
*/
②第二种写法,按照价值来进行dp:
当然,它也有它的使用范围拉,当价值的范围很小的时候才能用这个,而且用这个算法,它的复杂度和背包容量没有关系,所以说我们是根据价值来写的。
算法复杂度是O(n*m) 其中m是背包价值的总和,所以很显然,背包价值范围很小,就用这个。
其中dp[i] = k,表示的是,已经装了价值为 i 的物品所需要的最小体积k。
类似的题有HDU 2955,但这道题掺杂了一点概率。
完全类似的是牛客网上的一道题:
https://www.nowcoder.com/acm/contest/119/F
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define ll long long
using namespace std;
ll dp[10000005];
ll val[105],v[105];
ll T,n,m;
int main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
int sum = 0;
for(int i=0;i<n;i++)
{
scanf("%lld%lld",&v[i],&val[i]);
sum += val[i];
}
memset(dp,0x3f3f3f3f,sizeof(dp));
dp[0] = 0; //一定不能省略
for(int i=0;i<n;i++)
for(int j=sum;j>=val[i];j--)
dp[j] = min(dp[j], dp[j - val[i]] + v[i]);
// int ans = -1;
for(int i=sum;i>=0;i--)//最后还需要遍历一遍,找出体积符合条件的最大价值
{
if(dp[i] <= m)
{
printf("%d\n",i);
break;
}
}
}
return 0;
}
③第三种也就是DFS+剪枝了,很少见到,但是最近做题还是遇到了。
试用于物品个数比较少,但是背包容量和物品价值都太大了,这时候显然用不成前两种方法。
关于这个有一个例题和解析。
https://blog.csdn.net/feynman1999/article/details/71123241
这个例题好像访问不了
然后我做的题目是计蒜客上面的
https://nanti.jisuanke.com/t/29374
解释见注释
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
typedef long long ll;
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = 1 ; k <= r ; k++ )
#define mem(a,val) memset(a,val,sizeof a)
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
using namespace std;
const int maxn = 110;
int n;
ll vol; //背包的体积
struct spe
{
ll vol,val; //物品所需要的体积和价值
};
spe p[maxn];
spe backsum[maxn];
ll ans;
void dfs( int th,ll val,ll volume ) //三个参数: 遍历到第几个物品, 当前价值和 、体积和
{
ans = max(ans,val);
for( int i = th+1 ; i <= n ; i++ )
{
if( volume+backsum[i].vol <= vol ) //如果后面直接装的下 ,剪枝①
{
ans = max(ans,val+backsum[i].val);
return;
}
if( p[i].vol+volume <= vol && val+backsum[i].val > ans ) //如果当前装的下,而且后面的加起来比ans大,说明后面的潜力大
dfs(i,val+p[i].val,volume+p[i].vol ); //if的第二个条件是剪枝②
}
}
bool cmp( spe a,spe b )
{
return a.vol > b.vol;
}
int main()
{
while( scanf("%d %lld",&n,&vol) == 2 )
{
mem(backsum,0);
ans = 0;
fori(1,n)
scanf("%lld %lld",&p[i].vol,&p[i].val);
sort(p+1,p+1+n,cmp);
backsum[n].val = p[n].val;
backsum[n].vol = p[n].vol;
for( int i = n-1 ; i >= 1 ; i-- ) //求后缀和,为剪枝做准备
{
backsum[i].val = p[i].val+backsum[i+1].val;
backsum[i].vol = p[i].vol+backsum[i+1].vol;
}
dfs(0,0,0);
printf("%lld\n",ans);
}
return 0;
}
/*
*/
那么,以上就是三种普通的01背包,如果遇到,差不多就直接上模板就行了。
模块二:多重背包、完全背包
完全背包:
完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
完全背包按其思路仍然可以用一个二维数组来写出:
f[i][v]=max{ f[i-1][v-k*c[i]]+k*w[i] } 其中0<=k*c[i]<=v
关于完全背包的,感觉自己没别人讲的好,只能推荐一下别人的文章了
https://www.cnblogs.com/Kalix/p/7622102.html
多重背包
多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:
f[i][v]=max{ f[i-1][v-k*c[i]]+k*w[i] } 其中0<=k<=n[i]
通俗的将,完全背包就是物品有无限个,而多重背包物品有很多个,但不是无限个。
它们与普通的01背包的区别在于一种物品有很多个,而普通的01背包一个物品只有一件
对于多重背包,我们还可以进行二进制优化(个人建议学了多重背包就把这个看一下,其实很简单)。
那么什么是二进制优化呢?
举个例子。假如给了我们 价值为 2,但是数量却是10 的物品,我们应该把10给拆开,要知道二进制可是能够表示任何数的,所以10 就是可以有1,2, 4,8之内的数把它组成,一开始我们选上 1了,然后让10-1=9,再选上2,9-2=7,在选上 4,7-4=3,
而这时的3<8了,所以我们就是可以得出 10由 1,2,4,3,来组成,就是这个数量为1,2,3,4的物品了,那么他们的价值是什么呢,是2,4,6,8,也就说给我们的价值为2,数量是10的这批货物,已经转化成了价值分别是2,4,6,8元的货物了,每种只有一件哎!!!!这就是二进制优化的思想。
那为什么会有完全背包和01 背包的不同使用加判断呢?原因也很简单啊,当数据很大,大于背包的容纳量时,我们就是在这个物品中取上几件就是了,取得量时不知道的,也就理解为无限的啦,这就是完全背包啦,反而小于容纳量的就是转化为01背包来处理就是了,可以大量的省时间。
关于二进制分解的模板如下,我也用的这个,如果对于我的讲解不懂的,也可以看看这个博客
https://blog.csdn.net/bentutut/article/details/77855318
下面是例题:
HDU 2844
很好的一道题啊,结合完全背包和多重背包
题目的意思:
第一行输入,n,m分别表示n种硬币,m表示总钱数。
第二行输入n个硬币的价值,和n个硬币的数量。
输出这些硬币能表示的所有在m之内的硬币种数。
我是将他们分解成完全背包和多重背包来做的,因为一些物品如果太多,那么就可以把他们当成无数个,剩下的当成多重背包,这样时间应该更短一些。
Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse and found there were some coins. He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
Input
The input contains several test cases. The first line of each test case contains two integers n(1 ≤ n ≤ 100),m(m ≤ 100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1 ≤ Ai ≤ 100000,1 ≤ Ci ≤ 1000). The last test case is followed by two zeros.
Output
For each test case output the answer on a single line.
Sample Input
3 10 1 2 4 2 1 1 2 5 1 4 2 1 0 0
Sample Output
8 4
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
#include<time.h>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define mem(a,val) memset(a,val,sizeof a)
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
int n,m;
struct spe
{
int val,number;
};
const int maxn = 1e5+5;
spe p[maxn];
int multi_bag[maxn];
int complete_bag[maxn];
int multicnt,completecnt;
bool vis[maxn];
int main()
{
while( scanf("%d %d",&n,&m) ==2 )
{
if( n == 0 && m == 0 )
break;
mem(vis,false);
vis[0] = true;
multicnt = 1;
completecnt = 1;
fori(1,n)
scanf("%d",&p[i].val);
fori(1,n)
scanf("%d",&p[i].number);
fori(1,n)
if( p[i].val*p[i].number >= m ) //放入完全背包处理
complete_bag[completecnt++] = i;
else //放入多重背包
{
for( int j = 1 ; j <= p[i].number ; j <<=1 )
{
multi_bag[multicnt++] = p[i].val*j; //进行二进制分解
p[i].number -= j;
}
if( p[i].number > 0 )
multi_bag[multicnt++] = p[i].val*p[i].number;
}
int pos;
for( int i = 1 ; i < completecnt ; i++ ) //完全背包
{
pos = complete_bag[i];
for( int j = p[pos].val ; j <= m ; j++ )
if( vis[ j-p[pos].val ] )
vis[j] = true;
}
for( int i = 1 ; i < multicnt ; i++ ) //多重背包
{
for( int j = m ; j >= multi_bag[i] ; j-- )
if( vis[ j-multi_bag[i] ] )
vis[j] = true;
}
int ans = 0;
fori(1,m)
if( vis[i] )
ans++;
printf("%d\n",ans);
}
return 0;
}
/*
1000
3 100
1 2
3 1
4 2
*/