动态规划的背包问题

16 篇文章 2 订阅

01背包问题

题目:有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。《算法笔记》

样例:
5 8 //n=5,V=8
3 5 1 2 2 //w[i]
4 5 2 1 3 //c[i]

如果暴力的话,复杂度是O(2^n),因此使用动态规划,复杂度为O(nV);
令dp[i][j]表示:前i个物品放入容量为j的背包所能获得的最大价值。
对于dp[i][j]:

  1. 放入第i件物品:则最大价值为dp[i-1][v-w[i]]+c[i]。
  2. 不放入第i件物品:则最大极值为dp[i-1][v]。

得到状态转化方程:

dp[i][v] = max{dp[i-1][v],dp[i-1][v-w[i]]+c[i]} (1<=i<=n,w[i]<=v<=V)

for(int i=1;i<=n;i++)	//观察到v的范围,注意初始时候dp全赋成0
	for(int v=V;v>=w[i];v--)
		dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);

在这里插入图片描述
通过上图以及代码可以发现,dp[i][v]只与dp[i-1][?]有关,因此当v从大到小遍历时,可以把dp[i][v]化简为dp[v]。
得到状态转移方程:

dp[v] = max{dp[v],dp[v-w[i]]+c[i]} (1<=i<=n,w[i]<=v<=V)

for(int i=1;i<=n;i++)	观察到v的范围,注意初始时候dp全赋成0
	for(int v=V;v>=w[i];v--)
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);

解决引出01背包问题的完整代码如下:

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

int main(){
	int n,V;
	scanf("%d%d",&n,&V);
	vector<int> w(n);
	vector<int> c(n);
	vector<int> dp(V+1,0);
	for(int i=0;i<n;i++){
		scanf("%d",&w[i]);
	}
	for(int i=0;i<n;i++){
		scanf("%d",&c[i]);
	}
	for(int i=0;i<n;i++)	
		for(int v=V;v>=w[i];v--)
			dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
	printf("%d\n",dp[V]);
	return 0;
}

例题:leetcode

在这里插入图片描述
思路:

dp[i][j]:从前i个数字选数能否使得其和为j,可以则为1不可以则为0,dp[i][0] = 1;

状态转移方程:

dp[i][v]=dp[i-1][v] | dp[i-1][v-nums[i]]; (1<=i<=n,nums[i]<=v<=整数数组和的一半)
同样可以利用滚动数组!dp[j]=dp[j] | dp[v-nums[i]]; 具体代码略.

完全背包问题

有n种物品,每种物品的单间重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件。

对比01背包问题,完全背包对于每种物品可以不拿也可以拿超过1件。设dp[i][v]表示前i个物品放入容量为j的背包所能获得的最大价值。和之前一样样。
对于dp[i][v]:

  1. 不拿第i件物品,则dp[i][v]=dp[i-1][v]。
  2. 拿第i件物品,则dp[i][v] = dp[i][v-w[i]]+c[i]。
    因此,可以得到状态转移方程

dp[i][v] = max{dp[i-1][v],dp[i][v-w[i]]+c[i]} (1<=i<=n,w[i]<=v<=V)
边界dp[i][0] = 0(0<=i<=n) 并且dp[0][v] = 0(0<=v<=V)

在这里插入图片描述
为了压缩数组,观察原状态转移方程,可以化简如下。

dp[v] = max{dp[v],dp[v-w[i]]+c[i]} (1<=i<=n,w[i]<=v<=V)
边界dp[i][0] = 0(0<=i<=n) 并且dp[0][v] = 0(0<=v<=V) v必须正向枚举

for(int i=1;i<=n;i++)
	for(int v=w[i];v<=V;v++)
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);

例题leetcode

在这里插入图片描述
思路:

dp[i][j]:从前i个硬币中选择能否组成j,可以则为最少所需要的个数,不可以设为一个大数;

状态转移方程:

dp[i][v]=min(dp[i-1][v],dp[i][v-nums[i]]+1); (1<=i<=n,nums[i]<=v<=所要组成的数)
同样可以利用滚动数组!dp[v]=min(dp[v],dp[v-nums[i]]+1);边界:dp[0]=0;

代码如下

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {    //完全背包问题
        int size = coins.size();
        vector<int> dp(amount+1,amount+1);  
        //不能组成则为amount+1,能组成则为最小硬币个数0,1,2...
        dp[0]=0;
        for(int i=0;i<size;i++)
            for(int j=coins[i];j<=amount;j++)
                dp[j] = min(dp[j],dp[j-coins[i]]+1);
        if(dp[amount]<=amount)
            return dp[amount];  
        else return -1; 
    }
};

分组背包

题目:有n件物品,每件物品的重量为w[i],价值为c[i]。这些物品被分为若干组,每组中的物品互相冲突,只能拿一样。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。《算法笔记》

对于上述问题,定义dp[i][j],表示前i组物品花费费用j能取得的最大价值。可以分为下列情况:

  1. 第i组不拿。对应的dp[i][j] = dp[i-1][j]
  2. 第i组拿第1件或者第2件…拿第k件,
    对应的dp[i-1][k-c[i][k]]+w[i][k](其中c[i][k]和w[i][k]分别表示第i组第k件的重量和价值)

伪代码

for 所有的组i
    for 最高价值j
        for 所有属于组i的物品k
            f[i][j]=max(f[i-1][j],f[i-1][j-c[k]]+w[k])	//物品k属于组i

可以逆序用滚动数组压缩成一维。

依赖背包

实际上也可以转换成用上述的背包问题解决。

例题:P1064 金明的预算方案

分析

转换成分组背包后,对于组内进行01背包或者直接暴力遍历。

组内暴力遍历AC代码

#include<cstdio>
#include<algorithm>
using namespace std;
/*对于每组的枚举可以使用01恰好背包解决https://www.luogu.com.cn/problemnew/solution/P1064*/
/*用滚动数组能方便很多*/
const int maxn = 32001;	//最大钱数
const int maxm = 61;	//最大购买物品数量

int v[maxm],p[maxm],q[maxm];
//价格,		重要度,q=0主件,否则表示主件编号  
int group[maxm][3];	//第i组的主件和另外两个附件,从1开始 
int c[maxm][5];//c[i][j] 第i组第j种方案的价格 
int w[maxm][5];//w[i][j] 第i组第j种方案的价格与重要度乘积 
int hashmap[maxm];	//原来编号映射到group后的编号 

int dp[maxm][maxn];
//首先,把有依附关系的都分到一组,共有K组 
//dp[k][j] = 从前k组中选价值不超过j的所能得到的最大收益
//由于题目每组最多只有3件物件,因此有5中情况.1:都不选.2:选择主.3:选择主附1.4选择主附2.5全选
//转移方程 dp[k][j] = max(dp[k-1][j],dp[k-1][j-c[i]]+w[i](c[i],w[i]是k中一种方案对应的价格和价值重要度积)}

int main(){
	int n,m;	//n总钱数,m希望购买的物品数 
	scanf("%d %d",&n,&m);
	int n_group = 0; 
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&v[i],&p[i],&q[i]);
		if(q[i]==0){
			group[++n_group][0]=i;	//记录每组的主件,先不记录附件(其主件可能还未输入) 
			hashmap[i] = n_group;
		} 
	}
	for(int i=1;i<=m;i++)	//记录附件 
		if(q[i]!=0)
			for(int j=1;j<=2;j++)	//插入附件 
				if(group[hashmap[q[i]]][j]==0){
					group[hashmap[q[i]]][j]=i;
					break;
				}
	for(int i=1;i<=n_group;i++){	//计算每组的排列 
		int a1=group[i][0],a2=group[i][1],a3=group[i][2];	//该组中每个物品的编号 
		c[i][1] = v[a1]; 		
		w[i][1] = v[a1]*p[a1];					//只选主
		if(a2!=0){
			c[i][2] = v[a1]+v[a2];	
			w[i][2] = v[a1]*p[a1]+v[a2]*p[a2];		//主,附1
		}		
		if(a3!=0){
			c[i][3] = v[a1]+v[a3];	
			w[i][3] = v[a1]*p[a1]+v[a3]*p[a3];			//主,附2
			c[i][4]=v[a1]+v[a2]+v[a3];
			w[i][4]=v[a1]*p[a1]+v[a2]*p[a2]+v[a3]*p[a3];	//全选 
		}
	}
	for(int i=1;i<=n_group;i++){
		for(int j=1;j<=n;j++){
			dp[i][j] = dp[i-1][j];	//本组什么都不选 
			for(int k=1;k<=4&&c[i][k]!=0;k++){	//遍历每组的方案
				if(c[i][k]<=j)
					dp[i][j] = max(dp[i][j],dp[i-1][j-c[i][k]]+w[i][k]);
			}
		}
	}
	printf("%d",dp[n_group][n]);
	return 0;
}

其他

背包问题九讲

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值