动态规划之01背包【从暴力到优化】

动态规划 ——DP 的介绍
动态规划 (DP) 是一种算法技术,它将大问题分解为更简单的子问题,对整体问题的最优解决方案取决于子问题的最优解决方案。

有的问题有 2个特征:重叠子问题、最优子结构。用 DP可以高效率地处理具有这 2个特征的问题。

处理 DP的大问题和小问题,有两种实现方式 ——自顶向下与记忆化递归 / 自下而上与制表递推。

以斐波那契为例,两种实现方式的代码分别如下:

// 自顶向下与记忆化递归
int memoize[maxn];   //保存结果
int fib (int n){
    
    
    if (n==1 || n==2)  
        return 1;
    if(memoize[n] != 0)    //直接返回保存的结果,不再递归
        return memoize[n];    
    memoize[n]= fib(n-1) + fib(n-2);   //递归计算结果,并记忆
    return memoize[n];
}
// 自下而上与制表递推
const int maxn = 255;
int dp[maxn];
int fib (int n){
    
    
    dp[1] = dp[2] =1;
    for (int i=3; i<=n; i++)
        dp[i] = dp[i-1] + dp[i-2];
    return dp[n];
}

两种实现方式的复杂度相同,但是第二种更为常用,超过四维(dp[ ][ ][ ][ ])的表格也是常用的。

核心要点
在这里插入图片描述

01背包
01背包问题最暴力的一个解法:二维动态规划

准备工作:
设第 i个物品的体积是 v[i],价值是 w[i],f[i][j]表示只看前 i个物品,“可用(剩余)的 ”总体积是j的情况下,总价值最大是多少。

result = max{f[n][0~v]}

状态的转移:
每个 i对应的 f[i][j],可以看作一个集合的划分,也就是当前拿与不拿对应两种状态:

当前拿了的话,状态就是:dp[i-1][j-v[i]]+w[i] (+之前的是前i-1个物品对应的最大价值) ; 没拿的话,状态就是:dp[i-1][j]。

f[i][j] = max{1,2} (1和 2分别表示两种状态)

合法的初始化: f[0][0] = 0 一个物品都不选的情况下,总体积和价值都为 0

集合如何划分
一般原则:
不重不漏,不重不一定都要满足 (一般求个数时要满足)。
如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来。

例题
在这里插入图片描述

AC代码

#include <bits/stdc++.h>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std;
const int N = 1010;

int n,m;
int f[N][N];
int v[N],w[N];

int main()
{
    
    
	cin>>n>>m;
	for(int i=1; i<=n; i++)  cin>>v[i]>>w[i];
	
	for(int i=1; i<=n; 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]);
		} 
	}
	    
	int res = 0;
	for(int i=0; i<=m; i++)  res = max(res,f[n][i]);
	
	cout<<res<<endl;
	return 0 ;
}

01背包的优化

每一个状态只与它的前一个状态有关,不需要把所有的状态记录下来,所以可以用一个 通用的优化方式——滚动数组 进行优化 (针对这道题,可以用一维数组去优化) 。尽量对代码进行 等价代换

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

1. f[i] 仅用到了f[i-1], 
2. j与j-v[i] 均小于j
3. 若用到上一层的状态时,从大到小枚举, 反之从小到大

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main() 
{
    
    
    cin >> n >> m;
    for(int i=1; i<=n; i++)  cin>>v[i]>>w[i];
    
    for(int i=1; i<=n; i++) 
        for(int j=m; j>=v[i]; j--) 
            f[j] = max(f[j], f[j-v[i]]+w[i]);
            
    cout<<f[m]<<endl;
    return 0;    
}

猜你喜欢

转载自blog.csdn.net/Luoxiaobaia/article/details/108693386