【动态规划】C++解决01背包问题(模板01背包、分割等和子集、目标和、最后一块石头的重量)

本文主要是介绍【动态规划】C++解决01背包问题(模板01背包、分割等和子集、目标和、最后一块石头的重量),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 前言
  • 2. 算法题
    • 2.1_【模板】01背包
    • 2.2_分割等和子集
    • 2.3_目标和
    • 2.4_最后一块石头的重量II

1. 前言

关于 动态规划的理解 与例题,点击👇

【动态规划】C++解决斐波那契模型题目(三步问题、爬楼梯、解码方法…)

有了上面的经验,我们来解下面 01 背包问题

2. 算法题

2.1_【模板】01背包

在这里插入图片描述

思路

  1. 设置状态表示

    • 对于此类背包问题,我们需要考虑的因素往往不止一个,状态表示会根据影响结果的因素而定
    • dp[i][j]:从前i个物品进行选择,所选体积不超过j的最大价值
  2. 写状态转移方程

    在这里插入图片描述

  3. 初始化

    • 虚拟空间一行一列,并初始化为0(因为会用max更新dp值)
  4. 填表的顺序

    • 从上向下 填表即可
  5. 返回值

    • dp[n][V]

代码

#include <iostream>
#include <string>using namespace std;// 定义全局变量 自动初始化为0
const int N = 1001;
int w[N], v[N], n , V; // n个物品 体积为V
int dp[N][N]; // dp数组: 自动初始化为0// 01背包
int main()
{// 读数据cin >> n >> V;for(int i = 1; i <= n; ++i)cin >> v[i] >> w[i];// 第一问// dp[i][j]:从前i个物品进行选择,所选体积不超过j的最大价值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;// 第二问// 初始化dpfor(int j = 1; j <= V; ++j) dp[0][j] = -1; // -1表示无效选法// 填表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;return 0;
}

2.2_分割等和子集

在这里插入图片描述

思路

  • 题意分析
    • 题目要求判断是否可以将数组分割成两个元素和相同的子集,即每个子集的元素和为sum(数组总和) / 2
    • 我们可以对题目进行转化,即只要能在数组中找到子集使其和为sum/2,那么就一定有另一个和自己元素和相同的子集
    • 即在数组中找到和为sum/2的元素选法个数,即01背包
  1. 设置状态表示

    • 根据题目,要求判断是否可以将数组分割,所以dp表类型设置为bool
    • dp[i][j]:以i为结尾的子数组中所有的选法中,是否有总和为j的
  2. 写状态转移方程
    在这里插入图片描述

  3. 初始化
    在这里插入图片描述

  4. 填表的顺序

    • 从上向下填写每行
  5. 返回值

    • dp[n][sum/2]

代码

class Solution {
public:bool canPartition(vector<int>& nums) {// 题目转化:找数,使和为sum/2int sum = 0, n = nums.size();for(auto num : nums)    sum += num; // 数组和if(sum % 2 == 1)    return false; // 奇数,不能分割int aim = sum / 2;// 创建dp数组:dp[i][j]: 以i为结尾的子数组中,总和是否为jvector<vector<bool>> dp(n+1, vector<bool>(aim+1));// 初始化 + 填表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){dp[i][j] = dp[i-1][j]; // 不选i位置数if(j >= nums[i-1]) // 映射下标dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];}return dp[n][aim];}
};

2.3_目标和

在这里插入图片描述

思路

  • 题意分析
    • 根据题目,即由x个正数与y个负数可以组成目标值target
    • 那么有:x - y = target,x + y = sum(数组和)
    • 则 x = (target + sum) / 2
    • 此时题目可以理解成,从数组中选择数,数的总和为x,求总共的选法,即01背包:
  1. 设置状态表示
    • dp[i][j]:以i为结尾的子数组中和为j的选法的个数
  2. 写状态转移方程
    • 可以看出本题与上题的总体差别不大,根据状态表示的不同,状态转移方程和初始化进行简单改动:

在这里插入图片描述

  1. 初始化

    • 只需要初始化第一行,dp[0][0] = 0,dp[0][j] = 1(j >= 1)
  2. 填表的顺序

    • 从上向下
  3. 返回值

    • dp[n][aim]

代码

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {// 题目转化:从数组中选择一些数,使其和为目标值,求选法的个数int n = nums.size(), sum = 0; // 数组和for(auto x : nums) sum += x;// a: 正数和    b: 负数和(绝对值)// a + b = sum; a - b = targetint aim = (sum + target) / 2; if(aim < 0 || (sum + target) % 2 == 1) return 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]; // 不选i位置数if(j >= nums[i-1]) dp[i][j] += dp[i-1][j-nums[i-1]];}return dp[n][aim];}
};

2.4_最后一块石头的重量II

在这里插入图片描述

思路

  • 题意分析
    • 观察题目,石头碰撞的过程实际就是,两个数相减的过程;
    • 要想使最后的重量最小,只需要在数组中找到序列总和尽可能接近sum/2,此时与剩下的相减的值就是最小的
    • 即转化为了01背包问题;
  1. 设置状态表示

    • dp[i][j]:以i为结尾的子数组中,总和不大于j的最大和(<=j)
  2. 写状态转移方程

    在这里插入图片描述

  3. 初始化

    • 初始化第一行为0
  4. 填表的顺序

    • 从上往下
  5. 返回值

    • sum - (2*dp[n][sum / 2])

代码

int lastStoneWeightII(vector<int>& stones) {// 题目转化为: 在数组中选数,使其总和最接近sum/2int sum = 0, n = stones.size();for(auto x : stones) sum += x;int aim = sum / 2;// 创建dp数组: dp[i][j]:从前i个数中选数,使其和最接近j时的值vector<vector<int>> dp(n+1, vector<int>(aim+1));for(int i = 1; i <= n; ++i)for(int j = 0; j <= aim; ++j){dp[i][j] = dp[i-1][j]; // 不选i数if(j >= stones[i-1]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i-1]] + stones[i-1]);}return sum - 2*dp[n][aim];}

这篇关于【动态规划】C++解决01背包问题(模板01背包、分割等和子集、目标和、最后一块石头的重量)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1030277

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

poj2576(二维背包)

题意:n个人分成两组,两组人数只差小于1 , 并且体重只差最小 对于人数要求恰好装满,对于体重要求尽量多,一开始没做出来,看了下解题,按照自己的感觉写,然后a了 状态转移方程:dp[i][j] = max(dp[i][j],dp[i-1][j-c[k]]+c[k]);其中i表示人数,j表示背包容量,k表示输入的体重的 代码如下: #include<iostream>#include<

hdu2159(二维背包)

这是我的第一道二维背包题,没想到自己一下子就A了,但是代码写的比较乱,下面的代码是我有重新修改的 状态转移:dp[i][j] = max(dp[i][j], dp[i-1][j-c[z]]+v[z]); 其中dp[i][j]表示,打了i个怪物,消耗j的耐力值,所得到的最大经验值 代码如下: #include<iostream>#include<algorithm>#include<

csu(背包的变形题)

题目链接 这是一道背包的变形题目。好题呀 题意:给n个怪物,m个人,每个人的魔法消耗和魔法伤害不同,求打死所有怪物所需的魔法 #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>//#include<u>#include<map

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,