DP经典例题-背包专题

1.什么是背包问题

背包的基本模型:
    给你一个容量为V的背包和若干种物品,在一定的限制条件下(每种物品都占用一定容量),问最多能放进多少价值的物品?

2.01背包

题意:有n个重量和价值分别为wi,vi,从这些物品中挑选总重量不超过W的物品,使其价值最大,输出价值最大值(其中每个物品最多只能放一件);

这题其实用dfs也可以做,但超时

①、确认子问题和状态
01背包问题需要求解的就是,为了重量为W的背包中物体总价值最大化,N件物品中第i件应该放入背包中吗?(其中每个物品最多只能放一件);
为此,我们定义一个二维数组,其中每个元素代表一个状态,即前i个物体中若干个放入重量为W背包中最大价值。数组为:dp[N][V],其中dp[i][j]表示前i件中若干个物品放入体积为j的背包中的最大价值
 ②、初始状态
dp[i][j] = 0;

转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);也就是两种状态,第i个物品放还是不放(也就是前i件中是否加入第i件产品进入体积为j的背包),放则为:dp[i-1][j-w[i]]+v[i]

不放则为:dp[i-1][j]

#include<iostream>
using namespace std;

const int maxn = 1000+10;
int dp[maxn][maxn] = {0};
int w[maxn];
int v[maxn];

/*
因为我需要从上层推下层,如果dp数组下标从0开始会比较麻烦点,所以这里我从1开始
*/
int main()
{
    int N,V;
    cin >> N >> V;
    for(int i = 1;i <= N;++i)
    {
        cin >> v[i] >> w[i];
    }
    
    for(int i = 1;i <= N;++i)
    {
        for(int j = 1;j <= V;++j)
        {
            //只有>=才会有两种选择,放或不放
            if(j >= v[i])
            {
               dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            
                //放了则表示为dp[i-1][j-v[i]]+w[i]
                
            }
            else
               dp[i][j] = dp[i-1][j];
        }
    }
    /*
	这里我觉得会给大家一个错觉,认为dp[N][V]就是最大,其实我们应该遍历dp[n][i]得到最大
	再输出。这里为何会直接输出dp[N][V],试想dp[i][j]与dp[i][j-1]相比,如果j-1可以放入第i件
	,那么j必定可以,所以这里其实已经保持最大了,所以只需要输出dp[N][V] 
	*/ 
    cout << dp[N][V] << endl;
    return 0;
}

当W = 12,n = 5,

v[6] = {0 , 2 , 5 , 3 , 10 , 4};    //价值
w[6]   = {0 , 1 , 3 , 2 , 6 , 2};     //重量

此题空间可以优化:大家看看这张表,再看看转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);我们可以用滚动数组,因为前i件只取决前i-1件的状态(不取决i-2,i-3之类的),也就无需保存所有的状态,只需保存当前状态的上一层状态,这里注意此时W需要从大到小来遍历,为什么呢?请看

优化后的转移方程:dp[j] = max(dp[j],dp[j-w[i]]+v[i]);

未优化后的转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

当W从小到大来,dp[j-w[i]]取到的已不是前i-1的状态而是前i的状态,你可以想想,j从小到大变化是不是把dp[j]给修改,而这里修改是在前i的状态下,当dp[j] = max(dp[j],dp[j-w[i]]+v[i])中dp[j-w[i]]取到的j是已被修改的,相当于二维中的dp[i][j-w[i]]而不是dp[i-1][j-w[i]]

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<map>
#include<set>
#include<cmath>
#include<algorithm>
using namespace std;

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

3.完全背包

N件物品,容量位W的背包。第i件物品的重量为wi,价值为vi,每件物品有无数个,求装的最大价值

同样,定义一个二维数组dp[i][j],表示取前i件中的若干件,在容量为j的背包中的最大价值

dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]);  (0<=k<=W/wi),这里k是从0开始

显然时间复杂度比较大,所以需要优化,推导如下:

1.dp[i][j] = max(dp[i-1][j-k*w[i]]);    k>=0

2.dp[i][j] = max(dp[i-1][j],dp[i-1][j-k*w[i]]);  k>=1

3.dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]-k*w[i]]);  k>=0

将1和3

1.dp[i][j] = max(dp[i-1][j-k*w[i]]);    k>=0

3.dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]-k*w[i]]);  k>=0

dp[i-1][j-w[i]-k*w[i]]和dp[i-1][j-k*w[i]]等价,所以把j-w[i]当成j,所以1.dp[i][j] = max(dp[i-1][j-k*w[i]])就变成dp[i][j-w[i]] = max(dp[i-1][j-k*w[i]]),而dp[i-1][j-k*w[i]]可以代替2中的dp[i-1][j-k*w[i]],2转化为dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]])

可能大家被上面的转换转晕了,没事大家可以看我在时间优化里写的注释,相信大家绝对看的懂。

此时还可以优化空间,也是用滚动数组了,只不过此时j不必4从大到小了,因为max(dp[i-1][j],dp[i][j-w[i]]),这里是前i种状态

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1000+10;
int dp[maxn][maxn]={0}; 
int main()
{

	int w[maxn];
	int v[maxn];
	int W;
    int n;
    scanf("%d",&n);scanf("%d",&W);
    for(int i = 1;i <= n;++i)
       scanf("%d%d",&w[i],&v[i]);
    
    for(int i = 1;i <= n;++i)
    {
    	for(int j = 0;j <= W;++j)
    	{
    		for(int k = 0;k*w[i] <=j;++k)
    		{
    			dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
    		}
    	}
    }
    printf("%d\n",dp[n][W]);	
    return 0;
}

 

时间优化后:

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;


int main()
{
	int dp[105][105]={0}; 
	int w[105];
	int v[105];
	int W;
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;++i)
       scanf("%d%d",&w[i],&v[i]);
    scanf("%d",&W);
    
    
    /*
	f[i,j]=max(f[i-1][j],f[i-1][j-w]+v,f[i-1][j-2w]+2v,f[i-1][j-3w]+3v,......)
        f[i,j-v] = max(      f[i-1][j-w],  f[i-1][j-2w]+v ,f[i-1][j-3w]+2v,......)
        可以发现f[i-1][j-w]+v,f[i-1][j-2w]+2v,f[i-1][j-3w]+3v,......这一堆等价于f[i,j-w]+v
        所以f[i,j]=max(f[i-1][j],f[i,j-w]+v);所以我们可以把k去掉
	*/
    for(int i = 1;i <= n;++i)
    {
    	for(int j = 0;j <= W;++j)
    	{
    		if(j < w[i])
			    dp[i][j] = dp[i-1][j];
			else 
    			dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
    	
    	}
    }
    
    
    
    printf("%d\n",dp[W]);	
    return 0;
}

空间优化:

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;


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

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值