背包九讲练习篇(更新中)

背包九讲:背包问题九讲 (moyujiang.com)

题单:背包问题 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一:01背包

1:原理及模板题

下面代码的注释阐述了原理

[NOIP2005 普及组] 采药 - 洛谷

/*例题:https://www.luogu.com.cn/problem/P1048
01背包
设F[i][v]表示将前i件物品放入容量为v的背包中时最大收益值
C[i]为第i件物品花费
W[i]为第i件物品的价值
则有F[i][v]=max(F[i-1][v],F[i-1][v-C[i]]+W[i])
即可以取,也可以不取,取的话就从背包容量为v-C[i]到v,因为这件物品重C[i]
i从1~N,v从C[i]~V (小于C[i]放不下) 
空间优化:F[v]表示已经装了体积为v的物品时最大收益值,i从1~N,但v从V~C[i],这样的话F[v]由F[v-C[i]]得到 
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品 
	for(int v=V;v>=c;v--){//不要写成v>=0 
			f[v]=max(f[v],f[v-c]+w);
	}
}
/*使用时可以这么用:
	for(int i=1;i<=N;i++)
		zeroOnePack(F,C[i],W[i]);
*/
int main(){
	scanf("%d%d",&V,&N);
	for(int i=1;i<=N;i++){
		int c=0,w=0;
		scanf("%d%d",&c,&w);
		if(c>V){
			N--;
			i--;
			continue;
		}
		C[i]=c;W[i]=w;
	}
	for(int i=1;i<=N;i++){
		zeroOnePack(F,C[i],W[i]);
	}
	printf("%d",F[V]);
	return 0;
}

2:背包计数

这里解释一下初始化:装满什么都不取的空背包有且仅有一种解决方案;剩下的都是0,比如装满什么都不取的体积为3的背包,解决方案是0 

P1164 小A点菜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<cstdio>//https://www.luogu.com.cn/problem/P1164 
#include<iostream>//背包方案计数
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int sum(int a,int b){
	return a+b;
}
void zeroOnePack(int f[],int c){//取一件物品 
	for(int v=V;v>=c;v--){//不要写成v>=0 
		f[v]=sum(f[v],f[v-c]);
	}
}
int main(){
	F[0]=1;
	scanf("%d%d",&N,&V);
	for(int i=1;i<=N;i++){
		int c=0;
		scanf("%d",&c);
		if(c>V){
			N--;
			i--;
			continue;
		}
		C[i]=c;
	}
	for(int i=1;i<=N;i++){
		zeroOnePack(F,C[i]);
	}
	printf("%d",F[V]);
	return 0;
}

 3:01变体

P1049 [NOIP2001 普及组] 装箱问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

乍一看这题少了一维,需要重新推公式有点麻烦,其实根据高数思想,少一维度就令那个维度为单位长度即可,也就是说让少的价值维度W[i]单位化。考虑到求箱子剩余空间,我们干脆令物品的价值等于其体积即可,1体积=1价值,W=C。最后求V-F[V]即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品 
	for(int v=V;v>=c;v--){//不要写成v>=0 
			f[v]=max(f[v],f[v-c]+w);
	}
}
int main(){
	scanf("%d%d",&V,&N);
	for(int i=1;i<=N;i++){
		int c=0;
		scanf("%d",&c);
		if(c>V){
			N--;
			i--;
			continue;
		}
		C[i]=W[i]=c;
	}
	for(int i=1;i<=N;i++){
		zeroOnePack(F,C[i],W[i]);
	}
	printf("%d",V-F[V]);
	return 0;
}

P1060 [NOIP2006 普及组] 开心的金明 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这题没啥说的,W[i]=W[i]*C[i]罢了

#include<cstdio>//https://www.luogu.com.cn/problem/P1060 
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品 
	for(int v=V;v>=c;v--){//不要写成v>=0 
			f[v]=max(f[v],f[v-c]+w);
	}
}
int main(){
	scanf("%d%d",&V,&N);
	for(int i=1;i<=N;i++){
		int c=0,w=0;
		scanf("%d%d",&c,&w);
		if(c>V){
			N--;
			i--;
			continue;
		}
		C[i]=c;W[i]=w*c;
	}
	for(int i=1;i<=N;i++){
		zeroOnePack(F,C[i],W[i]);
	}
	printf("%d",F[V]);
	return 0;
}

4 体积压缩

这题恶心在体积太大了,数组开不了这么大,我们需要压缩 

Luogu P3985 不开心的金明 - do_while_true - 博客园 (cnblogs.com)

这个大佬写的很好

二 完全背包

1:稍坑模板题

P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这道题的坑在于:

F需要long long,后面printf忘了lld,WA了我好几次没找到原因

//完全背包https://www.luogu.com.cn/problem/P1616
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int read();
const int MAXN=1e7+10;
long long F[MAXN];
int C[MAXN],W[MAXN];
int V,N;
void completePack(long long F[],int C,int W){
	for(int v=C;v<=V;v++)//易错:v from C to V instead of 1
		F[v]=max(F[v],F[v-C]+W);
}
int main(){
	V=read(),N=read();
	for(int i=1;i<=N;i++){
		int c=read(),w=read();
		if(c>V){
			i--,N--;
			continue;
		}
		C[i]=c,W[i]=w;
	}
	for(int i=1;i<=N;i++)
		completePack(F,C[i],W[i]);
	printf("%lld",F[V]);
	return 0;
} 
int read(){
	char c;
	int s=0;
	int w=1;
	while(c<'0' || c>'9'){
		if(c=='-') w*=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		s=c-'0'+(s<<3)+(s<<1);
		c=getchar();
	}
	return w==1?s:-s;
}

P2722就纯小白模板了,略

2:体积逐次递增的完全背包

P1853 投资的最大效益 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这道题中,总体积每年都会增加,有两种思路,一种是每年都memset,一种是开二维数组,都可以,一定要注意除以1000时函数的细节,我被细节坑了好久,下面分别给出两种思路的AC代码

i memset版

//完全背包https://www.luogu.com.cn/problem/P1616
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int read();
const int MAXN=5e4+10;
int F[MAXN];
int C[10010],W[1010];
int V,N,Year;
void completePack(int F[],int C,int W){
	for(int v=C;v<=V/1000;v++){//易错:v from C to V instead of 1
		F[v]=max(F[v],F[v-C]+W);
	}
}
int main(){
	V=read(),Year=read(),N=read();
	for(int i=1;i<=N;i++){
		int c=read(),w=read();
		C[i]=c,W[i]=w;
	}
	for(int y=1;y<=Year;y++){
		memset(F,0,sizeof(F));
		for(int i=1;i<=N;i++){
			completePack(F,C[i]/1000,W[i]);		
		}	
		V+=F[V/1000];	
	}
	printf("%d",V);
	return 0;
} 
int read(){
	char c;
	int s=0;
	int w=1;
	while(c<'0' || c>'9'){
		if(c=='-') w*=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		s=c-'0'+(s<<3)+(s<<1);
		c=getchar();
	}
	return w==1?s:-s;
}

ii 二维数组版

//完全背包https://www.luogu.com.cn/problem/P1853
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int read();
const int MAXN=5e4+10;
int F[41][MAXN];
int C[10010],W[1010];
int V,N,Year;
void completePack(int Y,int C,int W){
	for(int v=C;v<=V/1000;v++){//易错:v from C to V instead of 1
		F[Y][v]=max(F[Y][v],F[Y][v-C]+W);
	}
}
int main(){
	V=read(),Year=read(),N=read();
	for(int i=1;i<=N;i++){
		int c=read(),w=read();
		C[i]=c,W[i]=w;
	}
	for(int y=1;y<=Year;y++){
		for(int i=1;i<=N;i++){
			completePack(y,C[i]/1000,W[i]);		
		}	
		V+=F[y][V/1000];	
	}
	printf("%d",V);
	return 0;
} 
int read(){
	char c;
	int s=0;
	int w=1;
	while(c<'0' || c>'9'){
		if(c=='-') w*=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		s=c-'0'+(s<<3)+(s<<1);
		c=getchar();
	}
	return w==1?s:-s;
}

3 求最小价值的完全背包

​​​​​​P2918 [USACO08NOV]Buying Hay S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这题把H当成V,P当成C,C当成W就转化成求最小价值的完全背包了

很简单,把状态转移方程的MAX转为MIN即可 

#include<cstdio>
#include<iostream>
using namespace std;
#define foreach(N) for(int i=1;i<=(N);i++)
int V,N;
const int MAXN=1e7+10;
int F[MAXN],C[MAXN],W[MAXN];
void completePack(int F[],int C,int W){
	for(int i=C;i<=V;i++)	F[i]=min(F[i],F[i-C]+W);
}
int read(){
	int s=0,w=1;
	char c=getchar();
	while(c<'0' || c>'9'){
		if(w=='-') w*=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		s=c-'0'+(s<<3)+(s<<1);
		c=getchar();
	}
	return s*w;
}
int main(){
	N=read(),V=read();
	foreach(N){
		int c=read(),w=read();
		if(c>V){
			i--,N--;
			continue;
		}
		C[i]=c,W[i]=w;
	}
	foreach(V) F[i]=0x3f3f3f3f; 
	foreach(N){
		completePack(F,C[i],W[i]);
	}
	printf("%d",F[V]);
	return 0;
}

三 多重背包

1:混合背包转为多重背包

P1833 樱花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

 混合背包,不过直接转多重背包处理也一样

多重背包转二进制优化原理是把共M件物品拆分为K件,这K件选与不选的所有组合等价于选0~M件,最终都是转为01背包。

#include<cstdio>
#include<iostream>
using namespace std;
#define foreach(N) for(int i=1;i<=(N);i++)
int V,N;
const int MAXN=1e7+10;
int F[MAXN],C[MAXN],W[MAXN],M[MAXN];
void completePack(int F[],int C,int W){
	for(int i=C;i<=V;i++)	F[i]=max(F[i],F[i-C]+W);
}
void zeroOnePack(int F[],int C,int W){
	for(int i=V;i>=C;i--)	F[i]=max(F[i],F[i-C]+W);
}
void multiplePack(int F[],int C,int W,int M){
	if(C*M>V){
		completePack(F,C,W);
		foreach(N)
		return;
	}
	int k=1;
	while(k<M){
		zeroOnePack(F,C*k,W*k);
		M-=k;
		k*=2;
	}
	zeroOnePack(F,C*M,W*M);
	
}
int read(){
	int s=0,w=1;
	char c=getchar();
	while(c<'0' || c>'9'){
		if(w=='-') w*=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		s=c-'0'+(s<<3)+(s<<1);
		c=getchar();
	}
	return s*w;
}
int main(){
	int a=read(),b=read(),c=read(),d=read();
	V=(c-a-1)*60+60-b+d;
	N=read();
	foreach(N){
		int c=read(),w=read(),m=read();
		if(c>V){
			i--;N--;
			continue;
		}
		C[i]=c,W[i]=w;
		if(!m) M[i]=1100;
		else M[i]=m;
	}
	foreach(N) multiplePack(F,C[i],W[i],M[i]); 
	printf("%d",F[V]);
	return 0;
}

2 求乘积的多重背包

​​​​​​P5365 [SNOI2017]英雄联盟 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

很有意思,遇到变式,我们第一件事就是先写状态转移方程(滚动处理前)

原方程是F[i][j]=max (F[i-1][j],F[i-1][j-C]+W)

而现在我们要求的是乘积

容易想到F[i][j]=max (F[i-1][j],F[i-1][j-C]*W)

构架有了,接下来就是带入参数了

显然W的数学意义是选取的件数,即W=1~K[i]

那么C就是选W件所花的钱,即C=C[i]*W=C[i]*K[i]

即F[i][j]=max (F[i-1][j],F[i-1][j-C*K]*K)

F[0]=1

本题务必注意long long

#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=1e6+10;
#define ll long long
#define foreach(N) for(int i=1;i<=(N);i++)
ll F[MAXN],M;
int C[1000],K[1000];
int V,N;
int read(){
	int s=0;
	char c=getchar();
	while(c<'0' || c>'9')
		c=getchar();
	while(c>='0' && c<='9'){
		s=(s<<3)+(s<<1)+c-'0';
		c=getchar();
	}
	return s;
}
ll readll(){
	ll s=0;
	char c=getchar();
	while(c<'0' || c>'9')
		c=getchar();
	while(c>='0' && c<='9'){
		s=(s<<3)+(s<<1)+c-'0';
		c=getchar();
	}
	return s;
}
void MultiplePack(ll F[],int C,int K){
	for(int i=V;i>=C;i--)
		for(ll j=1;j<=K && i-j*C>=0;j++)
			F[i]=max(F[i],F[i-j*C]*j);
} 
int main(){
	F[0]=1;
	N=read();M=readll();
	foreach(N)	K[i]=read();
	foreach(N)	C[i]=read();
	foreach(N)	V+=K[i]*C[i];	
	foreach(N)	MultiplePack(F,C[i],K[i]);
	foreach(V)
		if(F[i]>=M) {
			printf("%d",i);
			break; 
		} 
	return 0;
}

3 求总方案数的多重背包

模板是 F[i][j]=sum F[i-1][j]+F[i-1][j-C]

P1077 [NOIP2012 普及组] 摆花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

先求状态转移方程:设F[i][j]表示摆放第i种花,一共摆了j盆的方案总数
k从1到a[i],表示第i种花摆几盆
F[i][j]=sum(F[i-1][j],F[i][j-k])%p
滚动数组降低空间复杂度
F[j]=sum(F[j],F[j-k])%p
j从M到K
K从1到a
注意循环次序不能调换,先循环j再循环k,否则会造成在摆a[l]盆的基础上求摆a[l+1]盆的方案数,显然错误
函数调用时循环i从1到n

#include<cstdio>
int F[110],A[110];
int N,M;
const int p=1e6+7;
#define sum(a,b) ((a)+(b))
#define foreach(N) for(int i=1;i<=(N);i++)
void MultiplePack(int F[],int A){
	for(int j=M;j>=0;j--){
		for(int k=1;k<=A && j-k>=0;k++) 
			F[j]=sum(F[j],F[j-k])%p;
	}
}
int read(){
	int s=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9'){
		s=(s<<3)+(s<<1)+c-'0';
		c=getchar();
	}
	return s;
}
int main(){
	F[0]=1;
	N=read(),M=read();
	foreach(N) A[i]=read();
	foreach(N) MultiplePack(F,A[i]);
	printf("%d",F[M]);
	return 0;
}

4:求可行性的多重背包

​​​​​​P2347 [NOIP1996 提高组] 砝码称重 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

 有一个很有意思的解法

bitset解法

#include<cstdio>
#include<bitset>
using namespace std;
int W[]={1,2,3,5,10,20};
bitset<1001> S;
int main(){
	S[0]=1;
	for(int i=0;i<6;i++){
		int c;
		scanf("%d",&c);
		for(int j=0;j<c;j++){
			S|=S<<W[i];
		}
	}
	printf("Total=%d",S.count()-1);
}

C++ bitset类详解 (biancheng.net)

很有意思

多重背包解法

这跟上一题差别其实不大,只要对多重背包理解没问题按部就班也能写出来

#include<cstdio>//https://www.luogu.com.cn/problem/P2347 
#include<iostream>
using namespace std;
#define sum(a,b) ((a)+(b))
int F[1010],M[7],N,V;
void ZeroOnePack(int F[],int C){
	for(int i=V;i>=C;i--)	F[i]=sum(F[i],F[i-C]);
}
void CompletePack(int F[],int C){
	for(int i=C;i<=V;i++)	F[i]=sum(F[i],F[i-C]);
}
void MultiplePack(int F[],int C,int M){
	if(C*M>V){
		CompletePack(F,C);
		return;
	}
	int K=1;
	while(K<M){
		ZeroOnePack(F,C*K);
		M-=K;
		K*=2;
	}
	ZeroOnePack(F,C*M);
}
int main(){
	F[0]=1;
	int C[7]={0,1,2,3,5,10,20};
	for(int i=1;i<=6;i++){
		scanf("%d",M+i);
		V+=M[i]*C[i];
	}
	for(int i=1;i<=6;i++)	MultiplePack(F,C[i],M[i]);
	for(int i=1;i<=V;i++) if(F[i]) N++;
	printf("Total=%d",N);
}

还有一种思路供大家欣赏 

​​​​​​题解 P2347 【砝码称重】 - —千柒子的角落— - 洛谷博客 (luogu.com.cn)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值