当前位置:   article > 正文

背包问题_完全背包问题查询物体的个数

完全背包问题查询物体的个数

v[i]:价值
w[i]:重量
n:物品总数
m:背包容量

01背包问题

:一般是给两个值一个是价值,一个是重量,然后背包有承重上限,在承重上线下,问最多可以装多少价值的东西。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]);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

优化一下,用到了滚动数组,因为这里每一层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]);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一维数组为什么第二层循环是逆序:
如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = 30
  • 1
  • 2

此时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
  • 1
  • 2

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了

例题:

试题 算法提高 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;
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

    完全背包问题

    意思是有每个物品有无限个,选出给定重量的最大价值

    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]);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    要优化

    /*01背包*/dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);//与上一层比较
    //用的是上一层数据,所以要倒着第二层循环
    
    • 1
    • 2
    /*完全背包*/dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);//是与本层比较
    //用的是本层数据,新的数据,所以要顺着循环
    
    • 1
    • 2

    状态方程是和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]);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    多重背包

    相对与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]);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    二进制拆分思想
    将第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])
    	}	
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    求背包问题的排列和组合数

    核心状态转移方程:

    dp[j]+=dp[j-w[i]];
    
    • 1

    排列组合只是内外循环换个位置就行
    (组合不强调元素之间的顺序,排列强调元素之间的顺序)
    有顺序的排列数(外层循环是遍历背包容量,内层循环是遍历物品)
    如完全背包的排列数:(如下例三)

    for(int j=w[i];j<=m;j++)
    {
    	for(int i=1;i<=n;i++)
    	{
    		dp[j]+=dp[j-w[i]];
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    无顺序的组合数(外层循环是遍历物品,内层循环是遍历背包容量)
    如完全背包的组合数:(如下例二)

    for(int i=1;i<=n;i++)
    {
    	for(int j=w[i];j<=m;j++)
    	{
    		dp[j]+=dp[j-w[i]];
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    01背包组合数状态转移方程为

    for(int i=1;i<=n;i++)
    {
    	for(int j=m;j>=w[i];j--)
    	{
    		dp[j]+=dp[j-w[i]];
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    例一(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];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    例二:(完全背包组合数)
    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];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    例三(完全背包排列数)
    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];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
    推荐阅读
    相关标签
      

    闽ICP备14008679号