背包讲解——史上最细致版




背包问题,是dp的一类经典问题,一类模板大致分为9类,但其本质只有2种类型(01背包与完全背包)


一.非背包做法:

题目描述

一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。

输入

第1行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2至N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

输出

仅一行,一个数,表示最大总价值。

分析一下,得出以下爆搜代码:

​
#include<bits/stdc++.h>
using namespace std;
typedef double dd;
int a[25][2],x;
int c[25],b[25],ans=-999,n;
void dfs(int k,int sy,dd jz)
{
	if(k>n)
	{
		if(jz>ans)
		{
			ans=jz;
		}
		return;
	}
	if(sy>=a[k][0])  
	{
		b[k]=1;
		dfs(k+1,sy-a[k][0],jz+a[k][1]);//取当前的
		b[k]=0;
	}
	dfs(k+1,sy,jz);//不取
}
int main()
{
	cin>>x>>n;
	for(int i=1;i<=n;i++)
	 cin>>a[i][0]>>a[i][1]; 
	dfs(1,x,0);
	cout<<ans<<endl;
	return 0;
} 

​

 爆搜的复杂度可以到O(2^n),n>30是必爆时间的。

考虑优化:记忆化搜索,每次试一试当前物品,记录最大值,分类讨论取与不取

​
#include<bits/stdc++.h>
using namespace std;

int f[1001][1001] ,m ,ans;
struct node{
	int t,jz; 
}a[101];

int dfs(int ti,int k)//当前容量,递归层数
{
    if(f[ti][k]!=-1)  return f[ti][k];
	if(k>m)
	  return f[ti][k] = 0;//边界
	
	int ans1=-0x3f,ans2=-0x3f; 
	ans2=dfs(ti,k+1);
    if(ti-a[k].t>=0)//满足条件,取
      ans1=dfs(ti-a[k].t,k+1)+a[k].jz;
    return f[ti][k]=max(ans1,ans2);//取和不取都试试
}
int main()
{
	int t;
	cin>>t>>m;
	memset(f,-1,sizeof(f));
	for(int i=1;i<=m;i++)
	  cin>>a[i].t>>a[i].jz;
	cout<<dfs(t,1);
	return 0;
}

​

AC 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)



二.dp正题

(1).如果记忆化学的扎实的话,很容易推出f(i)(j)=f(i-1)(j-1),f(i-1)(j-v[i])  (v[i]<=j<=m)(1<=i<=n) 的关系式

下面给出分析:

设f(i)(j)表示取前i个物品,以m为背包重量的最大值,则:

当前轮只与前一轮相关(前i-2轮是定值,假设前i-2轮一定最优)

如果不取,当前背包容量与前面一轮的背包容量值相同 (j相同),所以f(i)(j)=f(i-1)(j);

如果取,当前背包的容量要减去当前物品的重量  (j不同),并加上价值,所以f(i)(j)=f(i-1)(j-v[i])+c[i];

属性为max,故f(i)(j)=max{f(i-1)(j-1),f(i-1)(j-v[i])}  (v[i]<=j<=m)(1<=i<=n)成立

代码:

#include<bits/stdc++.h>
using namespace std;

int n,m,w[201],c[201];
int f[35][201];
int main()
{
	ios::sync_with_stdio(false);
	cin>>m>>n;
	for(int i=1;i<=n;i++)  cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=m;j++){
			  f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
		}
	}
	cout<<f[n][m];
	return 0;
} 



该程序时间复杂度为O(nm),比暴力强多了

(2)

认真阅读“当前轮只与前一轮相关(前i-2轮是定值,假设前i-2轮一定最优)”

所以可以两个数组来回做(滚动数组),空间复杂度是O(n)的

不过这东西是真鸡肋

t=i%2;

f[t][j]=max(f[t^1][j],f[t^1][j-w[i]]+c[i]);

思维难度有的

boss,降一维数组:

f(i)表示以i为背包容量的最大价值

70 3

71 100

69 1

1 2

这是输入数据

代码一:

	cin >> t >> m;
	
	for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
	
	for(int i = 1; i <= m; i++){
		
		for(int j = t; j >= 1; j--) 
			dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
	}
	
	cout << dp[t];

输出 3

代码二:



int main(){
	
	cin >> t >> m;
	
	for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
	
	for(int i = 1; i <= m; i++){
		
		for(int j = 1; j <= t; j++) 
			dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
	}

输出0

  为什么? 这两个代码的第二层循环的顺序改变了,也就是对钱数的循环顺序变了,为什么一个小小的操作会有这么大的影响呢?因为我们在更新dp[i]数组时,用的是dp[i]和dp[i - w[i]]这两个状态,假如我们从1更新到M,那么当我们更新dp[i]时,dp[i - w[i]]已经被更新过了,它已经不是原来的那个dp[i - w[i]]了,很有可能dp[i - w[i]]已经买了一包第i种,然后我们更新dp[i]时调用dp[i - w[i]],又买了一样的,这不符合01背包。

虽然不符合01背包但这符合一个物品可取多次的情况,即完全背包问题



int main(){
	
	cin >> t >> m;
	
	for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
	
	for(int i = 1; i <= m; i++){
		
		for(int j = 1; j <= t; j++) 
			dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
	}


​

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值