背包DP

普通的背包模型

f[i][j] 表示从前i个物品选,体积不大于j的最大价值

01背包

423. 采药 - AcWing题库

426. 开心的金明 - AcWing题库

int f[N][M];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i-1][j];
			if(j>=w[i]){
				f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);
			}
		}
	}
	return f[n][m];
}
int f[M];
int dp1(){
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	return f[m];
}

完全背包

3. 完全背包问题 - AcWing题库

   完全背包用同层状态更新,压缩到一维时正序更新

int f[N][M];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i-1][j];
			if(j>=w[i]){
				f[i][j]=max(f[i][j],f[i][j-w[i]]+v[i]);
			}
		}
	}
	return f[n][m];
}
int f[M];
int dp1(){
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=m;j++){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	return f[m];
}

多重背包

4. 多重背包问题 I - AcWing题库

1019. 庆功会

int f[N][M];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i-1][j];
			for(int k=1;k<=s[i];k++){
				if(j>=k*w[i]){
					f[i][j]=max(f[i][j],f[i-1][j-k*w[i]]+k*v[i]);
				}
			}
		}
	}
	return f[n][m];
}
int f[M];
int dp1(){
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			for(int k=1;k<=s[i];k++){
				if(j>=k*w[i]){
					f[j]=max(f[j],f[j-k*w[i]]+k*v[i]);
				}
			}
		}
	}
	return f[m];
}

 5. 多重背包问题 II - AcWing题库

int f[M];
void init(){
	int cnt=0;
	for(int i=1;i<=n;i++){
		int a,b,s;cin>>a>>b>>s;
		int k=1;
		while(s>=k){
			cnt++;
			w[cnt]=k*a;
			v[cnt]=k*b;
			s-=k;
			k*=2;	
		}
		if(s>0){
			cnt++;
			w[cnt]=s*a;
			v[cnt]=s*b;
		}
	}
	n=cnt;
}
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	return f[m];
}

分组背包  (每组最多选一个)

9. 分组背包问题 - AcWing题库

int f[N][M];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){//注意要先枚举体积
			f[i][j]=f[i-1][j]; 
			for(int k=1;k<=s[i];k++){//最后枚举组内物品 
				if(j>=w[i][k])
					f[i][j]=max(f[i][j],f[i-1][j-w[i][k]]+v[i][k]);
			}
		}
	}
	return f[n][m];
}
int f[M];
int dp1(){
	for(int i=1;i<=n;i++){
		for(int j=m;j>=0;j--){//注意要先枚举体积,枚举到0
			for(int k=1;k<=s[i];k++){//最后枚举组内物品 
				if(j>=w[i][k])
					f[j]=max(f[j],f[j-w[i][k]]+v[i][k]);
			}
		}
	}
	return f[m];
}

二维费用的01背包

8. 二维费用的背包问题 - AcWing题库

int f[M1][M2];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=m1;j>=w1[i];j--){
			for(int k=m2;k>=w2[i];k--){
				f[j][k]=max(f[j][k],f[j-w1[i]][k-w2[i]]+v[i]);
			}
		}
	}
	return f[m1][m2];
}

1022. 宠物小精灵之收服

注意:在背包问题中,需要根据题意确定循环条件是否有等号,例如本题中第二维费用要求大于0,所以不能带等号

int v1[N],v2[N];
int f[M1][M2];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=m1;j>=v1[i];j--){
			for(int k=m2;k>v2[i];k--){
				f[j][k]=max(f[j][k],f[j-v1[i]][k-v2[i]]+1);
			}
		}
	}
	return f[m1][m2];
}

如果要找满足最大价值时,消耗某费用最少的状态,从后向前循环查找即可

int i;
for(i=m2;i>0;i--){
	if(f[m1][i]!=f[m1][m2]){
		break;
	}
}
cout<<m2-i;

方案数 01背包

278. 数字组合 - AcWing题库

题意:n个正整数,求组合成m的方案数

思路:每个正整数看作一个物品,体积为正整数的值

           f [i][j] 表示从前i个选,体积恰好为j的方案数

注意:求方案数时要将初始状态设置为1 ,即 f[0]=1

int f[M];
int dp(){
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j--){
			f[j]+=f[j-v[i]];
		}	
	}
	return f[m];
}

方案数 完全背包

1023. 买书

1021. 货币系统

求方案数的转移方式与相应的背包转移方式相同

int dp(){
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=v[i];j<=m;j++){
			f[j]+=f[j-v[i]];
		}	
	}
	return f[m];
}

 532. 货币系统 - AcWing题库

题意:求不能由其他数组成的数的个数

思路:求方案数,方案数为1的说明只能由自己组成

int dp(){
	memset(f,0,sizeof f);
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=v[i];j<=m;j++){//m为v[i]的最大值 
			f[j]+=f[j-v[i]];
		}	
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		//f[i]==1 表示i只能由i自己组成 
		if(f[v[i]]==1) cnt++;
	}
	return cnt;
}

混合背包

7. 混合背包问题 - AcWing题库

        根据物品所属的背包类别,依次处理每个物品。当该物品属于01背包时,可以看作次数为1的多重背包处理

int ww[N],vv[N];
int f[M];
int dp(){
	for(int i=1;i<=n;i++){
		int w,v,s;cin>>w>>v>>s;
		//完全背包 
		if(!s){
			for(int j=w;j<=m;j++){
				f[j]=max(f[j],f[j-w]+v);
			}
		}else{
			//01背包可以看作是次数为1的多重背包 
			if(s==-1) s=1;
			//多重背包
			int cnt=0; 
			int k=1;
			while(k<=s){
				++cnt;
				ww[cnt]=k*w;
				vv[cnt]=k*v;
				s-=k;
				k*=2;
			}
			if(s>0){
				++cnt;
				ww[cnt]=s*w;
				vv[cnt]=s*v;
			}
			for(int j=1;j<=cnt;j++){
				for(int k=m;k>=ww[j];k--){
					f[k]=max(f[k],f[k-ww[j]]+vv[j]);
				}
			}
		}
	}
	return f[m];
}

不同背包问题的初始化 (※

需要注意“初始化”和“转移条件”两个方面

 


二维费用背包的“至少”问题

1020. 潜水员

        一般的背包问题求的是费用不多于m的情况下,获得价值的最大值,而本题中询问的是 第一维费用不小于m1,第二维费用不小于m2的情况下,获得价值的最小值

        首先,由于求的是价值的最小值,我们需要将状态初始化为正无穷,且初始状态为0,即f[0]=0

        ②注意费用的条件是“不小于”,当费用为负数k时,表示费用不小于k的情况,依然符合条件(区别于一般背包费用的“不大于”条件,不存在费用<=负数的情况)。

        费用至少需要j的f[j],如果物品费用k大于j,此时k满足不小于费用j的条件取完物品后,转移到f[0]表示原来至少需要的费用j已经被满足,已经不再需要费用,即至少不小于0的情况

int dp(){
	memset(f,0x3f,sizeof f);
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=m1;j>=0;j--){
			for(int k=m2;k>=0;k--){
				f[j][k]=min(f[j][k],f[max(0,j-w1[i])][max(0,k-w2[i])]+v[i]);
			}
		}
	}
	return f[m1][m2];
}

输出背包方案

1013. 机器分配

题意:输出分组背包的方案

思路:从最终状态的费用开始,根据转移方程,在本题中是从最后一组开始,枚举选择的物品个数,来找到该组的方案,同时更新费用,再寻找前一组的方案

int v[N][M];
int f[N][M];
int dp(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i-1][j];
			for(int k=1;k<=j;k++){
				f[i][j]=max(f[i][j],f[i-1][j-k]+v[i][k]);
			}
		}
	}
	return f[n][m];
}
int res[N];
void print(){
	int j=m;
	for(int i=n;i>=1;i--){
		for(int k=0;k<=m;k++){
			if(j>=k&&f[i][j]==f[i-1][j-k]+v[i][k]){
				res[i]=k;
				j-=k;
				break;
			}
		}
	}
	for(int i=1;i<=n;i++){
		cout<<i<<" "<<res[i]<<endl;
	}
}

12. 背包问题求具体方案 - AcWing题库

01背包输出方案

题目要求输出最小字典序的方案,需要倒序DP,正序推方案(与一般情况正好相反)

//要求输出方案字典最小序
//倒序dp,正序推方案 
void dp(){
	for(int i=n;i>=1;i--){
		for(int j=0;j<=m;j++){
			f[i][j]=f[i+1][j];
			if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
		}
	} 
}
void print(){
	int j=m;
	for(int i=1;i<=n;i++){
		if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]){
			cout<<i<<" ";
			j-=v[i];
		}
	}
}

有依赖的背包问题

10. 有依赖的背包问题 - AcWing题库

题意:物品的关系构成一颗树,要想选某个物品,必须选上该物品的父物品

思路:可以当作是一个分组背包,每组中包含的是若干颗子树,对应若干个物品。对于组内的每个物品,先枚举可以给物品的体积j(预留出父物品的体积),然后枚举子物品要占的体积,最后对父物品选择。

int f[N][N];
//f[i][j] 以i为根节点的子树,体积不大于j的最大价值 
void dfs(int u){
	//相当于分组背包
	//遍历该组的所有物品 
	for(int i=h[u];~i;i=ne[i]){
		int son=e[i];
		dfs(son);
		//对该物品进行选择,物品其实是一颗子树 
		//枚举可以给子物品的体积(预留出父物品本身) 
		for(int j=m-w[u];j>=0;j--){
			//枚举子物品要占的体积
			//f[son][k] 以son为根节点的子树,体积不大于k的最大价值 
			for(int k=0;k<=j;k++){
				f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
			}
		}
	}
	//将父物品选上 
	for(int j=m;j>=w[u];j--){
		f[u][j]=f[u][j-w[u]]+v[u];
	}
	//如果不能选父物品,则不能选任何物品 
	for(int j=0;j<w[u];j++){
		f[u][j]=0;
	}
}
//dfs(root);
//cout<<f[root][m];

 487. 金明的预算方案 - AcWing题库

题意:物品分主件和附件,选择附件必须选择所属的主件

思路:对所有的主件进行遍历,在枚举体积时,对每种主件+附件的方案进行枚举选择

PII ms[N];//主件 
vector<PII> sv[N];//附件 
int f[M];
int dp(){
	for(int i=1;i<=n;i++){
		if(ms[i].v){//主件
			for(int j=m;j>=0;j--){
				//枚举所有选择附件的情况
				for(int k=0;k<1<<sv[i].size();k++){
					//主件+附件的体积和价值 
					int v=ms[i].v,w=ms[i].w;
					for(int z=0;z<sv[i].size();z++){
						if(k>>z&1){
							v+=sv[i][z].v;
							w+=sv[i][z].w;
						}
					}	
					if(j>=w){
						f[j]=max(f[j],f[j-w]+v);
					}
				}
			}
		}
	}
	return f[m];
}

背包最优解的方案数

11. 背包问题求方案数 - AcWing题库

思路:①在进行转移时,如果转移前后的价值相同,则对方案数进行累加

           ②最后将等于最大价值的方案数量进行累加即可

int f[N],g[N];
int dp(){
	g[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			int maxn=max(f[j],f[j-w[i]]+v[i]);
			int cnt=0;
			if(f[j]==maxn) cnt+=g[j];
			if(f[j-w[i]]+v[i]==maxn) cnt+=g[j-w[i]];
			g[j]=cnt%mod;
			f[j]=maxn;	
		}
	}
	int res=0;
	for(int j=m;j>=0;j--){
		if(f[j]==f[m]){
			res=(res+g[j])%mod;
		}else break;
	}
	return res;
}

贪心决策01背包

734. 能量石 - AcWing题库

题意:每个物品的费用是时间,价值会随着时间而减少

思路:采用贪心决策,按一定的规律对所有的物品进行排序

struct stone{
	int s;//体积 
	int e;//原始价值 
	int l;//每秒失去l价值 
	//贪心,根据s/l进行排序 
	bool operator<(const stone& t) const{
		return s*1.0/l<t.s*1.0/t.l;
	} 
}st[N];
int dp(){
	memset(f,0,sizeof f);
	for(int i=1;i<=n;i++){
		for(int j=m;j>=st[i].s;j--){
			f[j]=max(f[j],f[j-st[i].s]+st[i].e-(j-st[i].s)*st[i].l);
		}
	}
	int res=0;
	for(int i=1;i<=m;i++){
		res=max(res,f[i]);
	}
	return res;
}

逆向思维(代价与价值互换

  正常背包会超时,把代价与价值互换,f[i]表示拿到i金币花的最少时间

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5,M=3e4+5;
int f[M];//拿到i金币的最少时间 
int n,m;
int w[N],v[N];
int main(){
	cin>>n>>m;
	memset(f,0x3f,sizeof f);
	f[0]=0;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<=n;i++)
		for(int j=3e4;j>=v[i];j--)
			f[j]=min(f[j],f[j-v[i]]+w[i]);
	int res=0;
	for(int i=0;i<=3e4;i++)
		if(f[i]<=m) res=i;
	cout<<res;
}


题记 

整数拆分

3382. 整数拆分 - AcWing题库

        完全背包求方案数,一个数可以由若干个2的次幂组成,将2的次幂作为物品 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e6+5,mod=1e9;
ll f[N];
//w[i] 2^i
//完全背包 
int w[N],cnt;
int n;
void init(){
	for(ll d=1;d<=n;d*=2){
		w[++cnt]=d;
	}
}
int main(){
	cin>>n;
	init();
	f[0]=1;
	for(int i=1;i<=cnt;i++){
		for(int j=w[i];j<=n;j++){
			f[j]=(f[j]+f[j-w[i]])%mod;
		}
	}
	cout<<f[n];
} 

包子凑数

1226. 包子凑数 - AcWing

         结论:由若干个数组合的数一定是这些数的gcd的倍数,所以如果gcd不等于1,则不能组合的数有无限个,剩下的就是完全背包

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e4+5;
bool f[N];
int n;
int w[N];
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
int main(){
	cin>>n;
	int all_gcd=w[1];
	for(int i=1;i<=n;i++){
		cin>>w[i];
		all_gcd=gcd(all_gcd,w[i]);
	}
	//组合的数一定是全部数gcd的倍数 
	if(all_gcd!=1){//gcd!=1说明有无限数不能被组合 
		cout<<"INF";
		return 0;
	}
	f[0]=true;
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<N;j++){
			f[j]|=f[j-w[i]];
		}
	}
	int res=0;
	for(int i=1;i<N;i++){
		if(!f[i]){
			res++;
		} 
	}
	cout<<res;
} 

波动数列

1214. 波动数列 - AcWing

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e3+5,mod=1e8+7;
ll f[N][N];
int n,s,a,b;
//f[i][j] 加i项  和模n的余数为j  的方案数 
int get_mod(int a,int b){
	return ((a%b)+b)%b;
}
int main(){
	cin>>n>>s>>a>>b;
	f[0][0]=1;	
	for(int i=1;i<n;i++){
		for(int j=0;j<n;j++){
			//第i个选a     保持第i-1时模n的余数 加上(n-i)*a后模n的余数 为j  
			f[i][j]=(f[i][j]+f[i-1][get_mod(j-(n-i)*a,n)])%mod;
			//第i个选-b    保持第i-1时模n的余数 减去(n-i)*b后模n的余数 为j  
			f[i][j]=(f[i][j]+f[i-1][get_mod(j+(n-i)*b,n)])%mod;
		}
	}
	//序列长度为n 需要加n-1次 最终和的余数为s%n 
	cout<<f[n-1][get_mod(s,n)];
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vic.GoodLuck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值