赞
踩
v[i]:价值
w[i]:重量
n:物品总数
m:背包容量
:一般是给两个值一个是价值,一个是重量,然后背包有承重上限,在承重上线下,问最多可以装多少价值的东西。01的意思就是每个物体只有一个,只能装一次。
状态转移方程为:
dp[n]=max(dp[n],dp[n-1]+v[i]);
初始条件:
dp[i]=0;
如果题目给的价值有负数,那么非0下标就要初始化为负无穷了。例如:一个物品的价值是-2,但对应的位置依然初始化为0,那么取最大值的时候,就会取0而不是-2了,所以要初始化为负无穷。
先要这样想:dp[i][j](i是有前i个物品,j是当前能装物品重量)(总的意思是在容量为j,有i个物品的最大价值)
for(int i=1;i<=n;i++)//从第一个物品到第n个物品讨论
//循环可以交换位置
{
for(int j=1;j<=m&&j>w[i];j++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
优化一下,用到了滚动数组,因为这里每一层i都是和上一层i-1去比,每用一层i就要刷新一下这i层的数据,而下面的完全背包是与本层去比
for(int i=1;i<=n;i++)//n是物品个数,m是总承重重量
{//循环不可以交换位置,因为需要保证每个物品只添加一次
for(int j=m;j>=w[i];j--)//这里就要从后往前递推,避免数据被覆盖
{//滚动数组
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
一维数组为什么第二层循环是逆序:
如果正序遍历
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = 30
此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。
为什么倒叙遍历,就可以保证物品只放入一次呢?
倒叙就是先算dp[2]
dp[2] = dp[2 - weight[0]] + value[0] = 15 //(dp数组已经都初始化为0)
dp[1] = dp[1 - weight[0]] + value[0] = 15
所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了
:
例题:
试题 算法提高 01背包
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.
输入格式
输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
以后N行每行两个数Wi和Vi,表示物品的重量和价值
输出格式
输出1行,包含一个整数,表示最大价值。
样例输入
3 5
2 3
3 5
4 7
样例输出
8
数据规模和约定
1<=N<=200,M<=5000.
#include<iostream> #include<algorithm> #include<cstring> using namespace std; int w[5000];//重量 int v[5000];//价值 int dp[5000];//dp[i]为当前i重量的最大价值 int main() { int n,m; cin>>n>>m; memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { cin>>w[i]>>v[i]; } for(int i=1;i<=n;i++) { for(int j=m;j>=w[i];j--) { dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } cout<<dp[m]<<endl; }
意思是有每个物品有无限个,选出给定重量的最大价值
for(int i=1;i<=n;i++)//这个算法比较捞,用来3层循环
{
for(int j=m;j>=w[i];j--)
{
for(int k=0;k<=j/w[i];k++)//相对于01背包加了一个可以达到最大数量的循环
{
dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
}
}
}
要优化
/*01背包*/dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);//与上一层比较
//用的是上一层数据,所以要倒着第二层循环
/*完全背包*/dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);//是与本层比较
//用的是本层数据,新的数据,所以要顺着循环
状态方程是和01背包一样的,但是第二层循环方向不一样。
for(int i=1;i<=n;i++)
{
for(int j=w[i];j<=m;j++)//与01背包的区别
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
2649 完全背包
1.0 秒 131,072.0 KB 10 分 2级题
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是v[i],价值是c[i]。
现在请你选取一些物品装入背包,使这些物品的体积总和不超过背包容量,且价值总和最大。
其中1<=N<=100,1<=V<=50000,1<=v[i],c[i]<=10000。
输入
第一行输入两个数N,V,分别表示物品种类数和背包容积;
之后N行,每行两个数v[i],c[i],分别表示第i种物品的体积和价值;
输出
输出一个数,表示最大的价值
输入样例
2 11
2 3
6 14
输出样例
20
#include<iostream> #include<algorithm> #include<cstring> using namespace std; int w[5000],v[5000],dp[5000]; int main() { int n,m; cin>>n>>m; memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { cin>>w[i]>>v[i]; } for(int i=1;i<=n;i++) { for(int j=w[i];j<=m;j++) { dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } cout<<dp[m]<<endl; }
相对与01背包和完全背包,不同在于每个物体给定有限个个数
我们设这个每个不同物品的个数为 s[i]
状态转移方程式:dp[j]=max(dp[j],dp[j-kv[i]]+kw[i])
朴素的算法还是要仿照01背包
和完全背包的未优化前的三重循环相似,只是k的限制不一样
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
{
for(int k=0;k<=s[i]&&j>=k*v[i];k++)
{//从01背包的状态转移方程式,去增加i个物品拿k个的循环
dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
}
}
}
二进制拆分思想 |
---|
将第i个物品拆分成若干件物品,每件物品的体积和价值乘以一个拆分系数(1,21,22,…2k-1,Si-2k+1(最后这一项可能不是2的次方,是剩余数,与其它数加起来等于i))就可以转换为01背包的问题来求解,例如:Si=12,拆分系数为1,2,4,5转化为4件01背包的物品:(Vi,Wi),(2Vi,2Wi)(4Vi,4Wi)(5Vi,5Wi) |
如一个物品有12件,拆分系数为1,2,4,5 |
设sum为二进制拆分成为了多少个数拆分
vv[i],ww[i]分别为拆分后每个数的价值和重量
for(i=1;i<sum;i++)
{
for(j=m;j>=ww[i];j--)
{
dp[j]=max(dp[j],dp[j-ww[i]]+vv[i])
}
}
核心状态转移方程:
dp[j]+=dp[j-w[i]];
排列组合只是内外循环换个位置就行
(组合不强调元素之间的顺序,排列强调元素之间的顺序)
有顺序的排列数(外层循环是遍历背包容量,内层循环是遍历物品)
如完全背包的排列数:(如下例三)
for(int j=w[i];j<=m;j++)
{
for(int i=1;i<=n;i++)
{
dp[j]+=dp[j-w[i]];
}
}
无顺序的组合数(外层循环是遍历物品,内层循环是遍历背包容量)
如完全背包的组合数:(如下例二)
for(int i=1;i<=n;i++)
{
for(int j=w[i];j<=m;j++)
{
dp[j]+=dp[j-w[i]];
}
}
01背包组合数状态转移方程为
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
{
dp[j]+=dp[j-w[i]];
}
}
例一(01背包组合数)
leetcode494.目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { int n=nums.size(); int sum=0; for(int i=0;i<n;i++)//找所有数和 { sum+=nums[i]; } int l=(sum+target)/2;//找加或减的值,如示例得到3一定是4-1; vector<int> dp(l+1); if((sum+target)%2==1)//除不尽就不可能得到target { return 0; } dp[0]=1; for(int i=0;i<n;i++) { for(int j=l;j>=nums[i];j--)//01背包完全装满的方式数的固定套路 { dp[j]+=dp[j-nums[i]]; } } return dp[l]; } };
例二:(完全背包组合数)
leetcode518.零钱兑换II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
class Solution { public: int change(int amount, vector<int>& coins) { vector<int> dp(amount+1); int n=coins.size(); dp[0]=1; for(int i=0;i<n;i++) { for(int j=coins[i];j<=amount;j++) { dp[j]+=dp[j-coins[i]]; } } return dp[amount]; } };
例三(完全背包排列数)
leetcode377.组合总数IV
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3
输出:0
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1);
dp[0] = 1;
for (int i = 1; i <= target; i++) {
for (int num : nums) {
if (num <= i && dp[i - num] < INT_MAX - dp[i]) {
dp[i] += dp[i - num];
}
}
}
return dp[target];
}
};
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。