本文用的是DP,其中f[v]表示存储的是前i个物体放到容量v时的最大价值
一,0/1背包问题
详讲可以看
(https://blog.csdn.net/liusuangeng/article/details/38374405)
我是这看懂的
例题引入:
0/1背包
Description
给定n个物品和一个背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应该如何选择装入背包的物品,使得装入背包中物品的总价值最大?
Input
输入的第一行为测试样例的个数T,接下来有T个测试样例。
每个测试样例的第一行是物品个数n(1 ≤ n ≤ 3500)和背包容量C(C ≤ 13000),接下来n行,每行两个正整数 wi和 vi( wi ≤ 1000, vi ≤ 1000 ),分别表示第i件物品的重量 wi及其价值 vi。
Output
对应每个测试样例输出一行,只有一个整数,表示总价值的最大值。
Sample Input
2
1 2
1 1
2 3
2 3
2 4
Sample Output
1
4
一维滚动数组01背包模板:
#include
using namespace std;
/*有n件物品和一个容积为C的背包。1 ≤ N ≤ 1000 , 1 ≤ m ≤ 1000
第i件物品的重量w[i],价值是v[i]。*/
int main(void)
{
int n,C;
int w[1001]={0},v[1001]={0};
int f[13001];//n,m太大了用二维超空间了,应该用滚动数组 f[容量+1]
int i,j,t;
scanf("%d",&t);
while(t--)
{
memset(f,0,sizeof(f));//本题要求是在“不超过”背包容量的情况下,最多能获得多少价值或收益
//则数组f[]初始化为0;
//如果是 “恰好装满”背包的情况下,最多能获得多少价值或收益,则数组全初始话为负无穷,但是f[0]=0;
scanf("%d %d",&n,&C);//n个物体,m的容量
for(i=1;i<=n;i++)
scanf("%d %d",&w[i],&v[i]);
//01背包核心部分:
for(i=1;i<=n;i++)
for(j=C;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+v[i]);//max( 不挑选,挑选 );
printf("%d\n",f[C]);
}
return 0;
}
以下是用二维数组的01背包模板:
#include
using namespace std;
/*有n件物品和一个容积为C的背包。1 ≤ N ≤ 1000 , 1 ≤ m ≤ 1000
第i件物品的重量w[i],价值是v[i]。*/
int inf=-999999999;
int f[1001][1001];//数组太大了,必须设在这里
int main(void)
{
int n,C;
int w[1001]={0},v[1001]={0};
// int f[1001][1001];//n,m太大了用二维超空间了,应该用滚动数组 f[容量+1]
int i,j;
scanf("%d %d",&n,&C);//n个物体,m的容量
/*for(i=0;i<=n;i++)
for(j=0;j<=C;j++)
f[i][j]=inf;
for(i=0;i<=n;i++)
f[i][0]=0;
如果要求是在“恰好装满”背包容量的情况下,最多能获得多少价值或收益
则数组全初始话为负无穷,但是f[i][0]=0;*/
//但本题是 “不超过”背包的情况下,最多能获得多少价值或收益,则数组f[i][j]初始化为0;
memset(f,0,sizeof(f));
for(i=1;i<=n;i++)
scanf("%d %d",&w[i],&v[i]);
//01背包核心部分:
for(i=1;i<=n;i++)
for(j=0;j<=C;j++)
if(j
分析:
时间复杂度为O(n*C),如果体积很大但是价值很小,很容易超内存就是另外叫超大背包的问题了。
二,完全背包
解析(https://www.cnblogs.com/Kalix/p/7622102.html)
我觉得讲的不错,重点我标出来
1.“看完这个问题,你也许会觉得这个不就是01背包的升级版吗,其实就是这样,完全背包问题与01背包问题的区别在于完全背包每一件物品的数量都有无限个,而01背包每件物品数量只有1个
所以说与它相关的策略已经不是只有取和不取这两种策略了,而是有取0件、取1件、取2件……等等很多种策略”
2.“即:将一种物品拆成多件物品。
我们现在dp每一个物品,dp出该种物品在不同剩余容量下的最优解,他是以每1个为单位的。考虑是否在当前所有物品总数中添加一件新的该物品”
+
例题引入:
超市搞活动(完全背包)
Description
某超市举行活动,凡参加活动的市民,可以领到一个容量为C的箱子。超市里面的商品任意挑选,每种商品可拿的个数也无限制,只要能装进这个箱子(不超出箱子的容量),就可以免费拿走。
Input
多测试用例。
每个测试用例第一行是两个正整数 C 和 N ,( 0 < C ≤ 10,000, 0 < N < 10,000 ),C 表示箱子的容量,N 表示超市里面商品的总数量。
接下来 N 行,每行两个正整数 Pi 和 Qi ( 0 < Pi < 1000 , Qi > 0 , 1 ≤ i ≤ N ) ,表示第i件商品的价值和体积。
Output
每个测试用例输出一行:你能够免费拿走的商品的最大价值总和。
Sample Input
4 2
7 3
4 2
Sample Output
8
只要把“不超过”背包容量”01背包模板核心for循环改成顺序(即j从0~C)且修改状态方程即可
现在呈上完全背包的代码
#include
using namespace std;
int main(void)
{
int n,C;
int f[10001];
int i,j,w[10001],v[10001];
while(scanf("%d %d",&C,&n)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%d %d",&v[i],&w[i]);
memset(f,0,sizeof(f));
for(i=1;i<=n;i++)
for(j=w[i];j<=C;j++)
f[j]=max( f[j],f[j-w[i]]+v[i]);
printf("%d\n",f[C]);
}
return 0;
}
三,多重背包
例题引入:
多重背包
Description
给定N种物品和一个容量为C的背包,第i种物品最多有 Mi 件可用,每件的重量是Wi,价值是Vi。问:将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
Input
输入的第一行为测试样例的个数T,接下来有T个测试样例。
每个测试样例的第一行是物品种数N(1 ≤ N ≤ 100)和背包容量C(C ≤ 10000)。
接下来N行,每行三个正整数,Wi ,Vi 和 Mi ( Wi ≤ 10000, Vi ≤ 10000, Mi ≤ 10000 ),分别表示第i种物品的重量 Wi ,价值 Vi ,及个数 Mi 。
Output
对应每个测试样例输出一行,只有一个整数,表示装入背包的物品总价值的最大值。
Sample Input
1
2 8
4 100 2
2 100 4
Sample Output
400
多重背包和01背包、完全背包的区别:多重背包中每种物品的数量是给定的,可能不是一个,绝对不是无限个。
方法一:转化为01背包。
方法二:计算考虑k种物品承重限制为N时最大价值f[k][N]时,递推公式考虑两种情况,要么第 i 种物品一件也不放,就是f[i-1][j], 要么第 i 种物品放 k 件,其中 1 <= k <= (N/weight[i]),考虑这一共 k+1 种情况取其中的最大价值即为f[i][j]的值,即f[i][j] = max{f[i-1][j], (f[i-1][j-k*weight[i]]+kvalue[i])}。 这里为什么不能像完全背包一样直接考虑f[i][j-weight[i]]+value[i]呢?因为这样不容易判断第 i 种物品的个数是否超过限制数量 num[i]。
很遗憾当数据过大时,以上两种方法均超时。我们还有两种方法
1.二进制的优化
“二进制的优化
这是一个多重背包的模板,也是十分好用的一种模板,因为这个比直接拆除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元的货物了,每种只有一件哎!!!!这就是二进制优化的思想。”
附上我提交的代码,我用方法1和2都超时了
用二进制优化AC了
#include
using namespace std;
struct node{
int w;
int v;
};
int main(void)
{
int n,C;
int f[10001];
struct node wuping[20001];//一定小于2n;
int k,t,i,j,count,w,v,m;
scanf("%d",&t);
while(t--)
{
count=0;
memset(f,0,sizeof(f));
scanf("%d %d",&n,&C);
for(i=1;i<=n;i++){
scanf("%d %d %d",&w,&v,&m);//重量 价值 数量
k=1;
while(m-k>0)
{
m-=k;
wuping[++count].w =k*w;//重量翻倍
wuping[count].v =k*v;//价值翻倍
k*=2;//指数增长
}
wuping[++count].w =m*w;//剩下不足指数的数m
wuping[count].v =m*v;//不足指数的
}
//成功转换为01背包
for(i=1;i<=count;i++)//总数变为count
for(j=C;j>=wuping[i].w ;j--)//容量
f[j]=max( f[j],f[j-wuping[i].w ]+wuping[i].v );
printf("%d\n",f[C]);//f[容量]
}
return 0;
}
2.单调队列优化
(https://www.cnblogs.com/shuaihui520/p/9043143.html)
…这里提到还提到了另外一种优化:“那为什么会有完全背包和01 背包的不同使用加判断呢?原因也很简单啊,当数据很大w[i]*num,大于背包的容纳量时,我们就是在这个物品中取上几件就是了,这就是完全背包,反而小于容纳量(w[i]*num