闫氏DP求解01背包问题

01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N行,每行两个整数 v i v_i vi, w i w_i wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0 < N , V ≤ 1000 0<N,V≤1000 0<N,V1000
0 < v i , w i ≤ 1000 0<vi,wi≤1000 0<vi,wi1000


本文使用闫氏DP分析法——通过集合的角度来分析DP问题

分析步骤:

第一步:状态表示

这一步需要求出:

1、集合定义

2、集合里的元素的条件

3、集合元素的属性(Min/Max/Count)

4、f[i][j]具体含义

第二步:状态计算

这一步需要求出:

1、划分集合,根据最后一个不同点,最后一个有区分度的地方,将一个集合化为几个子集,
每个子集代表一类方案,划分时做到不重不漏
如果最后一个没有区分度,那就看 倒数第二个

2、找到每个子集的状态转移公式,如何通过子集转移到上一层的集合

第三步:确定边界

根据我们这个状态的含义,它的值应该是什么?怎么去初始化它,可以先画表格理解

详细步骤

先给出分析完之后的思维导图

在这里插入图片描述

第一步状态表示:

集合定义:

f[i][j]既表示每种状态也表示每一个集合

因为一共有2个不同的维度,第1个是每一个物品,第2个是每个物品的体积,

所以这就提示我们可以使用二维维度f[i,j]表示所有选法的集合

集合里的元素的条件:

1、从前i个物品中选

2、总体积<=j

所以结合定义和条件,f[i][j]具体含义:f[i][j]表示从前i个物品中选,且总体积<=j,的所有选法的集合

集合元素的属性:

f[i][j]存的是一个数,这个数是集合的某种属性(Min/Max/Count),
所以f[i][j]表示从前i个物品中选,且总体积<=j 的 所有选法 的价值最大值

接下来的集合划分、状态转移、边界处理都是从f[i][j]的含义出发来解决

第二步状态计算:

由题意结合f[i][j]的具体含义,f[N][V]就是我们要的答案,但f[i][j]所表示的状态如何被计算出来,通过集合划分

集合划分:

把f[i][j]表示的集合划分为若干个子集,使得每个子集都可以由前面的更小的子集表示,
每个子集代表一类选择方案

我们要求的是 每个子集(每种选法)的最大价值,这个最大价值就是所有从前i个物品中选且总体积<=j,
的所有方案的最大价值

划分技巧:

找到f[i][j]标识的所有方案里面,最后一个不同点,最后一个有区分度的地方,

在f[i][j]中最后一个有区分度的地方应该是最后一个物品如何选择:选择或不选择第i个物品

划分原则:

不重(求最值可以不满足)

不漏(一定要满足)

结合以上三点,将f[i][j]划分为两个子集:

第1个子集:

从前i个物品中选,不包含第i个物品,且总体积<=j的 所有选法的 集合

等价于从前i-1个物品中选总体积<=j的所有选法的集合

对应于f[i-1][j]

第2个子集:

从前i个物品中选,包含第i个物品,且总体积<=j的 所有选法 的集合

这个子集不能直接求,通过曲线救国的方式:

第一步:先假设所有方案都是包含第i个物品的,然后将所有方案同时去掉第i个物品,则物品数量
剩下i-1个物品,体积变为j-v[i](总体积减去第i个物品的体积),对应于f[i-1][j-v[i]]

第二步:在第一步的基础上,选上第i个物品,
就是在选i-1个物品的最大价值的基础上加上第i个物品的价值,对应于f[i-1][j-v_i]+w[i]

注意:当体积j装不下第i个物品时,即(j < v[i]),此时没得选,为空集

所以状态转移公式为:

if (j >= v[i])
	f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
else
	f[i][j] = f[i - 1][j];

第三步确定边界:

由f[i - 1][j],i需要>=1,所以需要确定一下f[0,0]~f[0,m]的值

因为f[0][0]表示一件物品都不选的最大价值,显然就是0

所以f[0,0]~f[0,m]=0 

最后 f[N][V] 就是要找的答案

代码

#include <iostream>      
#include <algorithm>      
#include <cstring>      

using namespace std;

const int ARRMAX = 1000 + 10;

// 物品数量 和 背包容积
int N, V;
// 每个物品体积 和 价值
int v[ARRMAX], w[ARRMAX];
// dp数组
int f[ARRMAX][ARRMAX];

int main()
{
	cin >> N >> V;
	for (int i = 1; i <= N; i++)
	{
		cin >> v[i] >> w[i];
	}

	// 枚举所有状态f[0~n,0~m]

	// 首先处理边界问题
	// 因为f[0,0]~f[0,m]表示一件物品都不选的 且 总体积不超过j的 所有选法 的集合
	// 当然这里开的全局数组,每个元素默认初始化为0,所以下面的for循环初始化可以不要
	for (int i = 0; i <= N; i++)
	{
		f[0][i] = 0;
	}

	for (int i = 1; i <= N; i++)
	{
		for (int j = 0; j <= V; j++)
		{
		    // 如果体积j能装下第i个物品(右边集合不为空集)
			if (j >= v[i])
			{
				// 对左右两边集合的最大值求max
				f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
			}
			else {
				// 左边的集合,当前方案的最大值
				f[i][j] = f[i - 1][j];
			}
		}
	}
	cout << f[N][V] << endl;

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值