DP:01背包问题

news2024/11/25 0:55:49

一、背包问题的概述

背包问题是⼀种组合优化的NP完全问题。
本质上是为了找出“带有限制条件的组合最优解”

1、根据物品的个数,分为如下几类:

• 01背包问题:每个物品只有⼀个(重点掌握)
• 完全背包问题:每个物品有无限多个(重点掌握)

• 多重背包问题:每件物品最多有n个
• 混合背包问题:每个物品会有上⾯三种情况
• 分组背包问题:物品有n组,每组物品⾥有若⼲个,每组⾥最多选⼀个物品

2、根据背包是否装满,⼜分为两类

• 不⼀定装满背包(重点)
• 背包⼀定装满(重点)

3、优化方案

• 空间优化:滚动数组(重点掌握)
• 单调队列优化
• 贪心优化

4、根据限定条件的个数,⼜分为两类

• 限定条件只有⼀个:比如体积->普通的背包问题(重点)
• 限定条件有两个:比如体积+重量->⼆维费用背包问题(重点)

5、根据不同的问法,⼜分为很多类:

• 输出方案
• 求方案总数
• 最优方案
• 方案可行性

        背包问题的题型非常多样,其中最重要以及基础的就是01背包和完全背包以及背包是否装满的讨论(会通过牛客的两道模版题探究),还有滚动数组的优化策略( 在以往的动态规划中,我们几乎很少去谈论空间优化,因为对于一道dp题来说,能解决出来就已经很不容易了,我们不太会关注其空间复杂度。但是在背包问题中,滚动数组的优化是有一定套路可言的,并且在某些情况下对时间也是有一定优化的!!

二、01背包[模版]

【模板】01背包_牛客题霸_牛客网

#include<iostream>
#include<string.h>
using namespace std;
//定义成全局,就不用在栈里面进行初始化,并且我们可以在栈上开辟的空间更大

const int N=1001;
int n,V,v[N],w[N];
int dp[N][N];

int main() 
{
   cin>>n>>V;//个数和体积
   for(int i=1;i<=n;++i) cin>>v[i]>>w[i];

   //解决第一问
   for(int i=1;i<=n;++i)
     for(int j=1;j<=V;++j)
      {
        dp[i][j]=dp[i-1][j];//不选第i个物品的情况
        if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
      }   
      cout<<dp[n][V]<<endl;
    //解决第二问
    memset(dp,0,sizeof dp);//修改成0
    //先进行初始化
    for(int j=1;j<=V;++j) dp[0][j]=-1;//跟0区分开
     for(int i=1;i<=n;++i)
     for(int j=1;j<=V;++j)
      {
        dp[i][j]=dp[i-1][j];//不选第i个物品的情况
        if(j>=v[i]&&dp[i-1][j-v[i]]!=-1) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
      }   
      cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;
}

滚动数组优化(空间复杂度N^2——>N   时间复杂度常数提升

#include<iostream>
#include<string.h>
using namespace std;
//定义成全局,就不用在栈里面进行初始化,并且我们可以在栈上开辟的空间更大
const int N=1001;
int n,V,v[N],w[N];
int dp[N][N];

int main() 
{
   cin>>n>>V;//个数和体积
   for(int i=1;i<=n;++i) cin>>v[i]>>w[i];

   //解决第一问
   for(int i=1;i<=n;++i)
     for(int j=V;j>=v[i];--j)
       dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
      cout<<dp[V]<<endl;
    //解决第二问
    memset(dp,0,sizeof dp);//修改成0
    //先进行初始化
    for(int j=1;j<=V;++j) dp[j]=-0x3f3f3f3f;//跟0区分开
     for(int i=1;i<=n;++i)
     for(int j=V;j>=v[i];--j)
         dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
      cout<<(dp[V]<0?0:dp[V])<<endl;
}

       对于不存在的状态,因为我们该题中要求的是max,所以我们设成-0x3f3f3f3f保证该状态不被选到,设置成这个的原因是避免了越界的风险同时又保证了不存在的状态是小于0的,且不会影响填报!!

三、和为目标和的最长子序列长度

. - 力扣(LeetCode)

       这题就是非常明显的01背包问题,其中每个数只有选或者不选,目标值相当于是容量,且要刚刚好。 dp[i][j]表示从前i个数选,和恰好为j的最长子序列。

class Solution {
public:
    int lengthOfLongestSubsequence(vector<int>& nums, int target) {
        int n=nums.size();
        //01背包问题  dp[i][j]表示从前i个数选择 正好凑成j的的子序列的最长长度
        vector<vector<int>> dp(n+1,vector<int>(target+1));
        //分析状态转移方程 dp[i][j] 
        //如果我不选i dp[i-1][j]
        //如果我选i   dp[i-1][j-nums[i-1]]+1 
        //初始化 如果i为0无数可选  没有这个状态
        for(int j=1;j<=target;++j) dp[0][j]=-0x3f3f3f3f;//给一个小的值  保证选最大值的时不会被选上
        for(int i=1;i<=n;++i)
          for(int j=0;j<=target;++j)
            {
                dp[i][j]=dp[i-1][j];
                if(j>=nums[i-1]) dp[i][j]=max(dp[i][j],dp[i-1][j-nums[i-1]]+1);
            }
            return dp[n][target]<0?-1:dp[n][target];
    }
};

滚动数组优化:

class Solution {
public:
    int lengthOfLongestSubsequence(vector<int>& nums, int target) {
        int n=nums.size();
        //01背包问题  dp[i][j]表示从前i个数选择 正好凑成j的的子序列的最长长度
        vector<int> dp(target+1,-0x3f3f3f3f);
        //分析状态转移方程 dp[i][j] 
        //如果我不选i dp[i-1][j]
        //如果我选i   dp[i-1][j-nums[i-1]]+1 
        //初始化 如果i为0无数可选  没有这个状态
        dp[0]=0;
        for(int i=1;i<=n;++i)
          for(int j=target;j>=nums[i-1];--j)
             dp[j]=max(dp[j],dp[j-nums[i-1]]+1);
            return dp[target]<0?-1:dp[target];
    }
};

四、分割等和子集(需转化)

. - 力扣(LeetCode)

该题并不能直接用01背包问题,首先需要先将问题进行转化——在数组中选一些数,让这些数的和为sum/2。 

class Solution {
public:
    bool canPartition(vector<int>& nums) 
    {
        int sum=accumulate(nums.begin(),nums.end(),0);
        if(sum%2) return false;//是奇数,直接返回
        //是偶数的时候 dp[i][j]表示从前i个数中选,所有选法中能否凑成j这个数
        int aim=sum/2;
        int n=nums.size();
        vector<vector<bool>> dp(n+1,vector<bool>(aim+1));
        //初始化,当j=0时,显然都是true  当i=0时,必然为false
        for(int i=0;i<=n;++i) dp[i][0]=true;
        //开始填表
        for(int i=1;i<=n;++i)
          for(int j=1;j<=aim;++j)
        //不选i的话  dp[i][j]=dp[i-1][j]
        //选i的话    dp[i][j]=dp[i-1][j-nums[i-1]]   前提j>=nums[i-1]
        {
            dp[i][j]=dp[i-1][j];
            if(j>=nums[i-1]) dp[i][j]=dp[i][j]||dp[i-1][j-nums[i-1]];
        }
        return dp[n][aim];
    }
};

滚动数组优化:

class Solution {
public:
    bool canPartition(vector<int>& nums) 
    {
        int sum=accumulate(nums.begin(),nums.end(),0);
        if(sum%2) return false;//是奇数,直接返回
        //是偶数的时候 dp[i][j]表示从前i个数中选,所有选法中能否凑成j这个数
        int aim=sum/2;
        int n=nums.size();
        vector<bool> dp(aim+1);
        //初始化,当j=0时,显然都是true  当i=0时,必然为false
        dp[0]=true;
        //开始填表
        for(int i=1;i<=n;++i)
          for(int j=aim;j>=nums[i-1];--j)
        //不选i的话  dp[i][j]=dp[i-1][j]
        //选i的话    dp[i][j]=dp[i-1][j-nums[i-1]]   前提j>=nums[i-1]
        dp[j]=dp[j]||dp[j-nums[i-1]];
        return dp[aim];
    }
};

 五、目标和(需转化)

. - 力扣(LeetCode)

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
       // 从nums中选择一些数能够凑成sum+target/2  转化成01背包问题
       int sum=accumulate(nums.begin(),nums.end(),0);
       int aim=(sum+target)>>1;
       if(aim<0||(sum+target)%2) return 0;
       int n=nums.size();
       //dp[i][j] 从前i个数选 变成j有多少种选法    
        //如果不选i dp[i-1][j]
        //如果选i   +=dp[i-1][j-nums[i-1]]
        //分析初始化 i=0的时候 必为0  j=0的时候 不好判断,因为nums[i]可能是0 
        //但是不需要初始化,因为要满足j>=nums[i] 那么nums[i]必然要为0才可以满足
        //所以绝对不会用到斜对角的值,而是只会用到上面的状态。
        vector<vector<int>> dp(n+1,vector<int>(aim+1));
        dp[0][0]=1;
       for(int i=1;i<=n;++i)
        for(int j=0;j<=aim;++j) 
        {
          dp[i][j]=dp[i-1][j];
          if(j>=nums[i-1]) dp[i][j]+=dp[i-1][j-nums[i-1]];
        }
        return dp[n][aim];
    }
};

 滚动数组优化:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
       // 从nums中选择一些数能够凑成sum+target/2  转化成01背包问题
       int sum=accumulate(nums.begin(),nums.end(),0);
       int aim=(sum+target)>>1;
       if(aim<0||(sum+target)%2) return 0;
       int n=nums.size();
       //dp[i][j] 从前i个数选 变成j有多少种选法    
        //如果不选i dp[i-1][j]
        //如果选i   +=dp[i-1][j-nums[i-1]]
        //分析初始化 i=0的时候 必为0  j=0的时候 不好判断,因为nums[i]可能是0 
        //但是不需要初始化,因为要满足j>=nums[i] 那么nums[i]必然要为0才可以满足
        //所以绝对不会用到斜对角的值,而是只会用到上面的状态。
        vector<int> dp(aim+1);
        dp[0]=1;
       for(int i=1;i<=n;++i)
        for(int j=aim;j>=nums[i-1];--j) 
            dp[j]+=dp[j-nums[i-1]];
        return dp[aim];
    }
};

六、最后一块石头的重量||(需转化)

. - 力扣(LeetCode)

class Solution {
public:
    int lastStoneWeightII(vector<int>& nums) {
      //让一堆里面的数尽可能接近sum/2
      int sum=accumulate(nums.begin(),nums.end(),0);
      int aim=sum/2,n=nums.size();
      //dp[i][j]表示从前i个数选择,总和不超过j,此时所有元素的最大和
      vector<vector<int>> dp(n+1,vector<int>(aim+1));
      //分析初始化 如果都为0 就返回0 如果i为0 也是0  如果j为0 不用初始化
      for(int i=1;i<=n;++i)
        for(int j=1;j<=aim;++j)
          {
            //如果不选i dp[i-1][j]
            //如果选i  dp[i-1][j-nums[i-1]] 找最大和
            dp[i][j]=dp[i-1][j];
            if(j>=nums[i-1]) dp[i][j]=max(dp[i][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
          }
        return sum-2*dp[n][aim];
    }
};

滚动数组优化:

class Solution {
public:
    int lastStoneWeightII(vector<int>& nums) {
      //让一堆里面的数尽可能接近sum/2
      int sum=accumulate(nums.begin(),nums.end(),0);
      int aim=sum/2,n=nums.size();
      //dp[i][j]表示从前i个数选择,总和不超过j,此时所有元素的最大和
      vector<int> dp(aim+1);
      //分析初始化 如果都为0 就返回0 如果i为0 也是0  如果j为0 不用初始化
        //如果不选i dp[i-1][j]
         //如果选i  dp[i-1][j-nums[i-1]] 找最大和
      for(int i=1;i<=n;++i)
        for(int j=aim;j>=nums[i-1];--j)
           dp[j]=max(dp[j],dp[j-nums[i-1]]+nums[i-1]);
        return sum-2*dp[aim];
    }
};

七、将一个数字表示成幂的和的方案数

. - 力扣(LeetCode)

知识点1:double不支持取模,需要取模又担心溢出只能使用long long

知识点2:pow函数是求数的任意次幂

知识点3:10^9+7相当于1e9+7

class Solution {
public:
    int numberOfWays(int n, int x) {
        //统计方案数
        //dp[i][j]表示从前i个数的x次幂之和  恰好等于j 的方案数
        //i=0时 无数可选 方案肯定是
        const int N=1e9+7;
        vector<vector<long long>> dp(n+1,vector<long long>(n+1)); //double不支持取模    
        dp[0][0]=1;
        for(int i=1;i<=n;++i)
          for(int j=0;j<=n;++j)
           {
            //不选i dp[i][j]=dp[i-1][j]
            //选i   dp[i][j]+=dp[i-1][j-pow(i,x)]
             dp[i][j]=dp[i-1][j];
             long long p=pow(i,x); 
             if(j>=p) dp[i][j]+=dp[i-1][j-p];
             dp[i][j]%=N;
           }
           return dp[n][n];
    }
};

 滚动数组优化:

class Solution {
public:
    int numberOfWays(int n, int x) {
        //统计方案数
        //dp[i][j]表示从前i个数的x次幂之和  恰好等于j 的方案数
        //i=0时 无数可选 方案肯定是
        const int N=1e9+7;
        vector<long long> dp(n+1); //double不支持取模    
        dp[0]=1;
        for(int i=1;i<=n;++i)
        {
            long long p=pow(i,x);
          for(int j=n;j>=p;--j)
            //不选i dp[i][j]=dp[i-1][j]
            //选i   dp[i][j]+=dp[i-1][j-pow(i,x)]
             dp[j]=(dp[j]+dp[j-p])%N;
        }
           return dp[n];
    }
};

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1830189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客热题:最长上升子序列(一)

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;最长上升子序列(一)题目链接方法…

用PHP来调用API给自己定制一个“每日新闻”

头条新闻汇聚了互联网上的时事动态&#xff0c;提供最新新闻动态、网络热门话题和视频更新等&#xff0c;覆盖社会、政治、体育、经济、娱乐、科技等多个领域&#xff0c;并不断刷新内容。企业应用这一接口后&#xff0c;可以快速吸引更多的用户访问自己的平台。即使是非新闻类…

WINUI——CommunityToolkit.Mvvm Messenger接收消息时报错:Cannot access a disposed object.

背景 WINUI开发时使用CommunityToolkit.Mvvm的Messemger让UI展示一些信息时出现错误&#xff1a; System.ObjectDisposedException:“Cannot access a disposed object. ObjectDisposed_ObjectName_Name” 详细见下述截图&#xff1a; 开发环境 WIN11 WINUI&#xff13; …

微信小程序开发系列(三十五)·自定义组件的属性properties

微信小程序开发系列&#xff08;三十四&#xff09;自定义组件的创建、注册以及使用&#xff08;数据和方法事件的使用&#xff09;_wx小程序组件开发-CSDN博客 目录 1. 组件的属性 2. 组件的使用 3. 细节描述 1. 组件的属性 Properties是指组件的对外属性&#xff0c;主…

Nginx之静态文件服务器的搭建

1.概述 静态文件服务器是指提供HTML文件访问或客户端 可直接从中下载文件的Web服务器。对于图片、 JavaScript或CSS文件等渲染页面外观的、不会动态改 变内容的文件&#xff0c;大多数网站会单独提供以静态文件服 务器的方式对其进行访问&#xff0c;实现动静分离的架构。 HTML…

ReactNative和Android通信

初始化一个RN项目以后&#xff0c;接下来想要让Android与React Native通信 写一个继承自ReactContextBaseJavaModule类的子类&#xff0c;重写getName方法 package com.awesomeprojectimport android.util.Log import android.widget.Toast import com.facebook.react.bridge.…

Java并发自测题

文章目录 一、什么是线程和进程?线程与进程的关系,区别及优缺点&#xff1f;二、为什么要使用多线程呢?三、说说线程的生命周期和状态?四、什么是线程死锁?如何预防和避免线程死锁?五、synchronized 关键字六、并发编程的三个重要特性七、JMM &#xff08;Java Memory Mod…

Android 自定义View

我们所有的试图都是起源于自定义View&#xff0c;包括ViewGroup也是继承于它&#xff0c;可以说它是视图组件之父。 我们可以从它的大致流程来分为四个部分&#xff1a; 构造方法&#xff0c;onMeasure&#xff0c;onLayout&#xff0c;onDraw 构造方法&#xff1a; 它主要有…

Java | Leetcode Java题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA null || headB null) {return null;}ListNode pA headA, pB headB;while (pA ! pB) {pA pA null ? headB : pA.next;pB …

Springmvc接收请求参数

如果你觉得这种限制很麻烦&#xff0c;你可以改为String 因为所有参数在接收的时候原值都是字符串

Mac | 崩溃分析

一、dump分析 1. 导入符号&#xff1a; ./import_pdb.sh libmedia_stream_ext.dylib.dSYM ./import_pdb.sh libowcr.framework.dSYM 2. 分析dump&#xff1a; ./analyze_dump.sh AE59D64F-0E1D-4A18-8DAF-C2C4D22F9FA6.dmp 3. 第 2 步骤 中会输出崩溃模块、崩溃线程及堆栈…

使用 Python 进行测试(5)测试的类型

总结 和我一起唱&#xff01; 冒烟测试&#xff0c;让你快速失败&#xff1b; 回归测试&#xff0c;不打破过去&#xff1b; 健全性检查&#xff0c;保留所拥有&#xff1b; 集成测试&#xff0c;处理副作用&#xff1b; 端到端&#xff0c;永无尽头&#xff01; 回测&#xf…

SwiftUI 6.0(Xcode 16)全新 @Entry 和 @Previewable 宏让开发妙趣横生

概览 如火如荼的 WWDC 2024 已进入第五天&#xff0c;苹果开发平台中众多海量新功能都争先恐后的喷薄欲出。 在这里就让我们从中挑两个轻松有趣的新功能展示给小伙伴们吧&#xff1a;它们分别是 全新的 Entry 和 Previewable 宏。 在本篇博文中&#xff0c;您将学到如下内容&a…

用React编写一个密码组件表单

theme: condensed-night-purple highlight: atelier-cave-light 背景介绍 我们在使用网站或者应用程序的登录界面或创建帐户界面时&#xff0c;往往避免不了需要用户输入密码这一步骤&#xff0c;而用户是否可以选择看见他们输入的密码是十分重要的一项功能。尤其是在当输入的…

Focusky是什么软件

Focusky是一款基于HTML5技术的多媒体演示软件&#xff0c;可以轻松地制作出生动有趣的PPT演示文稿、动画宣传片以及微课。与其他软件相比&#xff0c;Focusky拥有丰富的多媒体资源和动画效果&#xff0c;可以让演示内容更加生动。本文将为您详细介绍Focusky软件的功能&#xff…

awd工具安装

fscan(漏洞扫描) 下载 下载地址: Releases shadow1ng/fscan GitHub 把下载的文件放到指定文件目录里, 在文件的位置打开cmd 输入 fscan64.exe -h 192.168.1.1/24 ok了 接下来说说fscan的使用 使用 1.信息搜集: 存活探测(icmp) 端口扫描 2.爆破功能: 各类服务爆破(…

MongoDB~高可用集群介绍:复制集群(副本集)、分片集群

背景 MongoDB 的集群主要包括副本集&#xff08;Replica Set&#xff09;和分片集群&#xff08;Sharded Cluster&#xff09;两种类型。 副本集 组成&#xff1a;通常由一个主节点&#xff08;Primary&#xff09;和多个从节点&#xff08;Secondary&#xff09;构成。 功…

UniVue更新日志:使用ObservableList优化LoopList/LoopGrid组件的使用

github仓库 稳定版本仓库&#xff1a;https://github.com/Avalon712/UniVue 开发版本仓库&#xff1a;https://github.com/Avalon712/UniVue-Develop UniVue扩展框架-UniVue源生成器仓库&#xff1a;https://github.com/Avalon712/UniVue-SourceGenerator 更新说明 如果大家…

Django REST framework视图集与路由详解:深入理解ViewSet、ModelViewSet与路由映射器

系列文章目录 Django入门全攻略&#xff1a;从零搭建你的第一个Web项目Django ORM入门指南&#xff1a;从概念到实践&#xff0c;掌握模型创建、迁移与视图操作Django ORM实战&#xff1a;模型字段与元选项配置&#xff0c;以及链式过滤与QF查询详解Django ORM深度游&#xff…

【java】数学运算考试系统

目录 一、登录界面&#xff1a; 二、管理员界面&#xff1a; 三、学生考试界面&#xff1a; 面向小学低年级学生&#xff0c;随机生成两个整数的加减法算式要求学生解答。要求有用 户登录、注册等 GUI 界面&#xff0c;用户数据存入文件&#xff0c;体现面向对象编程思想。 …