01背包问题及其应用

01背包问题

有n个重量为w1,w2,w3…的物品,价值分别为v1,v2,v3…,现有一个容量为C的背包,问在不超过背包容量的条件下,所装物品的最大价值是多少?

这个问题有两个变量,分别为物品总数n,背包容量C。记 F ( n , C ) F(n,C) F(n,C)表示n件物品,C容量时的最大价值。考虑最后一步, F ( n ) F(n) F(n)能通过 F ( n − 1 ) F(n-1) F(n1)放与不放当前物品推导出来。
不放当前物品: F ( n , C ) = F ( n − 1 , C ) F(n,C)=F(n-1,C) F(n,C)=F(n1,C)
放当前物品: F ( n , C ) = m a x { F ( n − 1 , C ) , v [ n ] + F ( n − 1 , C − w [ n ] ) } F(n,C)=max\{F(n-1,C), v[n]+F(n-1,C-w[n])\} F(n,C)=max{F(n1,C),v[n]+F(n1,Cw[n])}

这并不是贪心算法,因为我们计算了 F ( n , C ) F(n,C) F(n,C)的所有可能情况,并取了最大值,而贪心只取最后一步的最大值。

递归方法

int zero_one_bag(vector<int>& w, vector<int>& v, int index, int cap)
{
	if (cap <= 0 || index == w.size())
		return 0;
	int res = zero_one_bag(w, v, index + 1, cap);
	if (w[index] <= cap)
		res = max(res,
				 zero_one_bag(w, v, index + 1, cap - w[index]) + v[index]);
	return res;
}

记录状态值(动态规划)

因为递归是不记录状态值的,所以会产生重复计算,因此把状态记录在数组里,如果状态值已经计算过,就直接返回。

其实这已经是动态规划。常见的动态规划是循环,这里递归改成循环就可以了。
空间复杂度 O ( n ∗ C ) O(n*C) O(nC)

vector<vector<int>> dp(w.size(),vector<int>(5 + 1,0));

int zero_one_bag_1(vector<int>& w, vector<int>& v, int index, int cap, vector<vector<int>>& dp)
{
	if (cap <= 0 || index <0)
		return 0;
	if (dp[index][cap] > 0) return dp[index][cap];
	int res = zero_one_bag_1(w, v, index - 1, cap, dp);
	if (w[index] <= cap)
		res = max(res, 
		zero_one_bag_1(w, v, index - 1, cap - w[index], dp) + v[index]);
	return res;
	
}

空间复杂度 O ( C ) O(C) O(C)

之前的方法使用二维数组做额外空间,其实 d p ( n ) dp(n) dp(n)只与 d p ( n − 1 ) dp(n-1) dp(n1)有关,所以只用长度为C+1的一维数组保存n-1时的状态int dp[C+1]。因为计算 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] } dp[i][j]=max\{dp[i-1][j], dp[i-1][j-w[i]]+v[i]\} dp[i][j]=max{dp[i1][j],dp[i1][jw[i]]+v[i]}需要用到数组前半部分的值,如果j从前向后计算会覆盖掉n-1时的值,所以从后向前计算。

int zero_one_bag_dp(vector<int>& w, vector<int>& v, int C)
{
	vector<int> dp(C + 1, 0);
	for (int i = 0; i < w.size(); i++)
	{//j >= w[i];是因为j< w[i]时,i的dp[j] = i-1的dp[j],也就是数组原来的值,不需要计算
		for (int j = C; j >= w[i]; j--)
		{
			dp[j] = max(dp[j], v[i] + dp[j - w[i]]);
		}
	}
	return dp.back();
}

计算 d p [ i ] [ j ] dp[i][j] dp[i][j]时一般逐行计算,可以优化掉i维度。如果 d p [ i , j ] − > d p [ i − 1 , j − k ] dp[i,j]->dp[i-1,j-k] dp[i,j]>dp[i1,jk]表示依赖于上一次(i-1)的前k位置(j-k),需要从后往前计算,避免覆盖,如背包; d p [ i , j ] − > d p [ i , j − k ] dp[i,j]->dp[i,j-k] dp[i,j]>dp[i,jk]表示依赖于当前次的之前k位置,需要覆盖掉上一次的值,所以从前往后计算。但是对于 d p [ i , j ] − > d p [ i − 1 , j − k ] , d p [ i , j − k ] dp[i,j]->dp[i-1,j-k], dp[i,j-k] dp[i,j]>dp[i1,jk],dp[i,jk]则无法优化。如果不清楚可以计算一下dp数组。

最多路径数 leetcode62
问题描述
从m*n数组的左上角到右下角,问总共有多少条不同的路径?
//dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]
//使用长度为n的一维数组,依赖于上一次j位置(本来就是未赋值之前)
//依赖于当前次,之前1位置,所以覆盖。
int[] dp = new int[n]; // 
   // 初始化
   for(int i = 0; i < n; i++){
     dp[i] = 1;
   }

       // 公式:dp[i] = dp[i-1] + dp[i]
   for (int i = 1; i < m; i++) {
       // 第 i 行第 0 列的初始值
       dp[0] = 1;
       for (int j = 1; j < n; j++) {
           dp[j] = dp[j-1] + dp[j];
       }
   }
   return dp[n-1];

应用

涉及元素选或不选都可以用01背包问题来解决。

  1. 相同子集和分割(leetcode 416)
    给定一个仅包含正整数的非空数组,确定该数组是否可以分成两部分,要求两部分的和相等.

分析:涉及元素选或不选,尝试用01背包解决。题目为是否存在子序列使其和=sum/2。以元素值作为重量和价值,因为包内元素重量<=sum/2。,所以价值总量<=sum/2;求最大价值,如果最大价值 = =sum/2,也即包内总重量==sum/2,符合条件。

class Solution {
public:
	bool canPartition(vector<int>& nums) {
		int s = 0;
		for (int i : nums) s += i;
		if (s % 2) return false;

		vector<int> dp(s / 2 + 1, 0);
		for (int i = 0; i < nums.size(); i++)
		{
			for (int j = s / 2; j >= nums[i]; j--)
			{
				dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
			}
		}
		return dp.back() == s / 2;
	}
};
  1. 双核cpu任务分配问题。(牛客

一种双核CPU的两个核能够同时的处理任务,现在有n个已知数据量的任务需要交给CPU处理,假设已知CPU的每个核1秒可以处理1kb,每个核同时只能处理一项任务。n个任务可以按照任意顺序放入CPU进行处理,现在需要设计一个方案让CPU处理完这批任务所需的时间最少,求这个最小的时间。

解析:
完成所有n个任务需要sum时间,总运行时间是固定的,假设第一个cpu处理时间为n1,第二个cpu时间为sum-n1,要使处理时间最小,则n1越来越靠近sum/2.我们以第一个核为背包,每个task运行时间为重量和价值,因为包内重量总是<=sum/2,即包内价值<=sum/2.所以求得包内价值最大值,就能让包内重量趋近于sum/2.

由于并行时间是两核运行的较大值,又因n1<=sum/2,所以返回sum/2- n1。

#include<iostream>
#include<vector>

using namespace std;

int main()
{
	int t = 0, n;
	cin >> n;
    vector<int> times(n, 0);
	for(int i=0;i<n;i++)
    {
        cin>>times[i];
        times[i]/=1024;
        t+=times[i];
    }
	vector<int> dp(t / 2 + 1, 0);


	for (int i = 0; i < times.size(); i++)
	{
		for (int j = t / 2; j >= times[i]; j--)
		{
			dp[j] = max(dp[j], dp[j - times[i]] + times[i]);
		}
	}
	cout << 1024 * ( t - dp.back());
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值