(死亡)
本文部分参照背包九讲(链接点这里)
先看三道题:
请记住这个题面:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
有相似题面的一系列一种组合优化的NP完全问题(附百度百科)就是背包问题。
背包的种类很多,大概(我会的)有01背包,完全背包,多重背包,分组背包。
从最基础的完全背包讲起。
完全背包
题面:
思路:
有F[u][v]用来存装了u种物品的背包里的最大价值。
对加入x个物品k的决策,有f[k][v]=f[k-1][v-c[k]*x]+w[k]*x
状态转移方程:f[k][v]=max{ f[k-1][v-c[k]*x]+w[k]*x (0<=k<=v/c[k]) }
时间复杂度O(VΣ(V/c))
空间复杂度O(NV)
先简单优化下:
拿两个的状态可以从拿一个的转移而来,后面以此类推
(仔细观察还可以再优化的亚子??)
优化代码大概长这样:
for(int i=1;i<=n;i++) { for(int j=w[i];j<=m;j++) f[j]=max(f[j-w[i]]+v[i],f[j]); }
完全背包就差不多了。。
画图来说就是这样的:
(二维)
01背包
题面:
有N种物品和一个容量为V的背包,每种物品都只有1件可用。第i种物品的费用是ci,价值是wi。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
跟完全背包差不多,完全背包看懂了这个也就自然OK
连代码都差不多的亚子,但是还是有一点不一样
先看代码找不同:
for(int i=1;i<=n;i++) { for(int j=m;j>=w[i];j--) f[j]=max(f[j-w[i]]+v[i],f[j]); }
为什么内循环是从大到小?
画图解释一下:
由此图可知:01背包依赖于已知前置位置
再改成一维的看看:
所以循环是从大到小
多重背包
题面:
有N种物品和一个容量为V的背包,每种物品都有n件可用。第i种物品的费用是ci,价值是wi。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
先介绍“二进制分组”
将一个自然数n分成m个数相加的形式,且1-n间的每一个数都可以被分成的m个数表示
例:
n=m1+m2+m3;
1-n中的所有数都可以用m1,m2,m3的和来表示
怎么实现就要用到万能的二进制了
将一个自然数n分成
n=a1*21+a2*22+a3*23+……+an*2n
这是会发现,这样并不能把所有自然数分完,特别是奇数,总会再剩一点。
所以设m=a1*21+a2*22+a3*23+……+an*2n
那么n就可以被表示成
n=a1*21+a2*22+a3*23+……+an*2n+(n-m)
而多重背包就是把n个物品二进制分组再重新处理它们的体积和价值,再用01背包即可。
代码:
for(int i=1;i<=n;i++) { int t=1; if(p[i]>1) { while(p[i]>t) { k++; w[n+k]=w[i]*t; v[n+k]=v[i]*t; p[n+k]=1; p[i]-=t; t*=2; } w[i]*=p[i]; v[i]*=p[i]; p[i]=1; } } for(int i=1;i<=n+k;i++) { if(p[i]==1) { for(int j=m;j>=w[i];j--) { f[j]=max(f[j],f[j-w[i]]+v[i]); } }
看一道例题:(点击收获RP++)
就很简单。。01背包就可以解决
AC代码:
#include<iostream> #include<cstdio> using namespace std; int v,c,n; int w[10001],f[10001],p[10001];// bool vis[10001]; int main() { scanf("%d%d%d",&v,&n,&c); for(int i=1;i<=n;i++) { scanf("%d%d",&w[i],&p[i]); } // f[0]=-0x7ffffff; for(int i=1;i<=n;i++) { for(int j=c;j>=p[i];j--) { f[j]=max(f[j],f[j-p[i]]+w[i]); } } for(int i=1;i<=c;i++) { if(f[i]>=v) { printf("%d",c-i); return 0; } } printf("Impossible"); return 0; }
(RP++!)