首页 > 其他分享 >动态规划

动态规划

时间:2024-10-24 17:22:16浏览次数:5  
标签:背包 序列 dp 物品 动态 规划 转移 DP

动态规划

概述

动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。

一般的解题步骤

解决动态规划问题,通常由两个步骤:

  1. 建立状态(阶段)定义。这个状态必须能描述一类问题的轮廓。例如在背包问题中,我们采用 dpi,j 来表示前 i 的物品,体积不超过 j 的最优解。但是能描述一类问题轮廓的状态可能不止一种,同时,并不是每一种状态都方便转移。如何找到一种优秀的状态定义,往往是动态规划问题的难点,但这也是有一定的套路的,对于经典的dp模型,我们可以很快的定义状态,例如背包问题这一类选择类dp,又或者是递增子序列这种序列类dp,这需要我们在做题中去总结和提炼。
  2. 设计状态转移。我们目前要求解的状态必须能从前面的状态转移过来,不然就无法求解。一个常见的套路就是,针对最后一个元素的决策和题目限制去设计。例如在背包问题中,对于最后一个物品,我们有选与不选两个决策。

常见的dp模型

背包DP(基础版)

01 背包

问题描述:有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

在上述问题中,每一个物品都有两种状态,选与不选。对于这种选择最优化类DP,我们常常使用dpi,j 来表示前 i 的物品,满足条件 j 的最优解。

第 i 个物品不选,转移方程为:\(dp_{i,j} = dp_{i-1,j}\)

第 i 个物品选,转移方程为:\(dp_{i,j} = dp_{i-1,j-v_i} + w_i\)

综上,转移方程为:\(dp_{i,j} = max(dp_{i-1,j-v_i} + w_i,dp_{i-1,j})\)

同时,由于对 \(f_i\) 有影响的只有 \(f_{i-1}\) ,因此可以优化掉一维,\(dp_{j} = max(dp_{j-v_i} + w_i,dp_{j})\),注意,如果优化掉第一维,枚举 j 的时候需要倒序枚举,避免使用同为 i 阶段的状态来更新。

完全背包

完全背包与01背包类似,区别为在完全背包中,每一个物品可以选用任意次。

采用类似于01背包的思想,决策由选与不选变为了选几个,我们可以得到状态转移方程为:$$dp_{i,j} = max^{+\infty}{k=0}(dp{i-1,j-kv_i} + kw_i,dp_{i-1,j})$$

同样的,可以优化掉一维,\(dp_{j} = max(dp_{j-v_i} + w_i,dp_{j})\),注意,此时枚举 j 的时候需要正序枚举,由于每个物品可以任意次使用,所以可以使用同为 i 阶段的状态来任意次更新。

多重背包

与01背包类似,区别为每一个物品有\(k_i\)个。决策依旧是选与不选变为了选几个,我们可以得到状态转移方程为:$$dp_{i,j} = max^{k_i}{k=0}(dp{i-1,j-kv_i} + kw_i,dp_{i-1,j})$$

同样的,可以优化掉一维,\(dp_{j} = max(dp_{j-v_i} + w_i,dp_{j})\),注意,此时枚举 j 的时候需要倒序枚举,因为每个物品的使用次数有限

多重背包的二进制优化

多重背包朴素算法的时间复杂度为\(O(W\sum{k_i})\),观察到我们可以将k个相同的物品拆分成\(log_2^k\)个大物品,可以证明这\(log_2^k\)个大物品可以组合成任意选法。时间复杂度为\(O(W\sum{log_2^{k_i}})\)

分组背包

问题描述:有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 \(v_{i,j}\),价值是 \(w_{i,j}\),其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

其实是从「在所有物品中选择一件」变成了「从当前组中选择一件」,决策变为选这组中的哪一个或不选。

使用dpi,j 来表示前 i 组,满足体积不超过 j 的最优解。

综上,转移方程为:\(dp_{i,j} = max(dp_{i-1,j-v_i} + w_i,dp_{i-1,j})\)

参考代码(引用自oi wiki):

for (int k = 1; k <= ts; k++)          // 循环每一组
  for (int i = m; i >= 0; i--)         // 循环背包容量
    for (int j = 1; j <= cnt[k]; j++)  // 循环该组的每一个物品
      if (i >= w[t[k][j]])             // 背包容量充足
        dp[i] = max(dp[i],
                    dp[i - w[t[k][j]]] + c[t[k][j]]);  // 像0-1背包一样状态转移

线性DP(基础版)

数字三角形

问题概述:给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

image-20241024141822978

这是一个典型的路径最优化DP,问题的关键在于找到一条路径,使得这条路径上的权值和最大。

对于此类问题,我们通常定义\(dp_{i,j}\)为从\((1,1)\)到\((i,j)\)的这条路径的最优解。

考虑到题目限制为只能向右下走或向左下走,所以\((i,j)\)只能从\((i-1,j)\)或\((i-1,j)\)转移,因此,状态转移方程为:\(dp_{i,j} = \max(dp_{i-1,j},dp_{i-1,j-1})+val_{i,j}\)


最长上升子序列

问题概述:给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

这是一类子序列DP问题,题目的限制为数值严格单调递增,因此我们需要在DP方程中记录当前子序列最后一个元素的数值,通常定义\(dp_i\)来表示前i的元素中,以第i的元素结尾的最优解。

可以得出,转移方程为:\(dp_i = \max_j^{j<i}(dp_j) + 1 \and val_j < val_i\)

时间复杂度:\(O(n^2)\)


最长上升子序列的贪心解法

对于不求具体的子序列,只求最长长度的最长上升子序列问题,DP法并不是最优秀的解法,有一种贪心的思想可以将时间复杂度降为\(O(nlogn)\)

下面给出贪心法求解上面问题的代码:

int main()
{
	vector<int> g;
    int n;
    cin>>n;
    for(int i = 1,x; i <= n; i ++)
    {
        cin>>x;
        if(g.empty())
        {
            g.push_back(x);
        }
        else 
        {
            int pos = lower_bound(g.begin(),g.end(),x) - g.begin();
            if(pos == g.size())
                g.push_back(x);
            else 
                g[pos] = x;
        }
    }
    cout<<g.size();
    return 0;
}

注意,如果题目要求非严格递增,则需要把lower_bound改为upper_bound!

最长公共子序列

问题概述:给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。

这是一类双序列DP问题,常用\(dp_{i,j}\)表示串A前i个元素与串B前j个元素的最优解。题目的限制为公共子序列,因此我们可以通过串A和串B最后的元素是否相同来构建转移方程。

相同时,转移方程为:\(dp_{i,j} = dp_{i-1,j-1} + 1\)

不同时,转移方程为:\(dp_{i,j} = max(dp_{i-1,j},dp_{i,j-1})\)

最短编辑距离

问题概述:给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

  1. 删除–将字符串 A 中的某个字符删除。
  2. 插入–在字符串 A 的某个位置插入某个字符。
  3. 替换–将字符串 A 中的某个字符替换为另一个字符。

现在请你求出,将 A 变为 B 至少需要进行多少次操作。

这是一类双序列DP问题,我们仍然用\(dp_{i,j}\)表示串A前i个元素与串B前j个元素的最优解。同时,串A和串B最后元素的决策题目已经给出,分别是删除、插入和替换,题目限制为将A变为B,因此,我们还是通过串A和串B最后的元素是否相同来构建转移方程。

相同时,不需要多余操作,转移方程为:\(dp_{i,j} = dp_{i-1,j-1}\)

不同时,枚举所有决策,转移方程为:\(dp_{i,j} = min(dp_{i-1,j},dp_{i-1,j-1},dp_{i,j-1})\)

注意初始化!

几道例题

例题一:(选自蓝桥:建造房屋)

image-20241024161516147

分析:每一个街道有m种选择,即建造多少个房屋。根据选择类DP常见的套路而言,我们使用\(dp_{i,j}\)来表示前i条街道,预算不超过k元的最优解。对于最后一个街道来说,它的决策有m种,即让\(1\leq i\leq m\)个房屋建造到该街道上。

综上,转移方程为:\(dp_{i,j} = \sum_{k=1}^m(dp_{i-1,j-k}) \and j-k \geq i-1\)

例题二:(选自蓝桥:可构造的序列总数)

image-20241024165047090

分析:首先我们可以定义\(dp_i\)用来描述当前构造的第几个数字(问题的轮廓),接下来观察题目限制,题目限制为非严格单调递增且\(a_i\)是\(a_{i-1}\)的倍数,所以我们要记录最后一个元素的数值来方便转移,观察到k的范围很小,因此我们再开一维,定义\(dp_{i,j}\)为前i个数且最后一个数为j的最优解。

其实这就是序列类DP,最长递增子序列问题的变种。因此,一定要理解基本的DP模型,很多状态定义的方法均来源于熟悉的DP模型中!

状态转移方程为:\(dp_{i,j} = \sum_{k=1}^j(dp_{i-1,k}) \and k | j\)

注意:朴素写法的时间复杂度为\(O(n^3)\),因此我们需要预先对约数预处理。

例题三:(选自蓝桥:最快洗车时间)

标签:背包,序列,dp,物品,动态,规划,转移,DP
From: https://www.cnblogs.com/zc-study-xcu/p/18500009

相关文章

  • 线性规划求解软件开发的PSP数据统计
    PSP报告1.计划(Planning)估算:本项目的主要目标是实现线性规划问题的优化模型,并通过GUI界面简化用户操作。根据任务复杂度,估算开发工作量约为40小时。2.开发(Development)2.1需求分析(Analysis)在项目中,需求包括以下几点:通过C++实现线性规划问题的优化模型。......
  • 局部路径规划(Local planning)算法之——TEB轨迹规划
    1TEB算法原理TEB全程为TimeElasticBand(时间弹力带),通过对给定的全局轨迹进行修正,从而优化机器人的局部运动轨迹。他是常用的局部路径规划方法之一。TEB是基于图优化的方法,以g2o优化框架实现,它以机器人在各个离散时间的位姿和离散时刻之间的时间间隔为顶点,通过多目标优化,包括......
  • 经典的二次规划问题的标准形式
    公式9-33描述的是经典的二次规划问题的标准形式,它是支持向量机(SVM)等机器学习算法以及许多凸优化问题中的核心问题。该公式描述了一个最小化目标函数的问题,并且附带有不等式约束和等式约束。具体形式如下:......
  • 【HarmonyOS】根据文本内容动态测算文本控件宽高
    【HarmonyOS】根据文本内容动态测算文本控件宽高问题背景:一般情况下,在鸿蒙里文本控件Text或者Span的宽高,我们都会设置固定宽高,或者根据内容自适应,不设置固定宽高。但是在特殊场景下,例如,父组件的宽高需要根据子组件的内容动态设置宽高。或者是文本控件根据内容会有行数变化。都需......
  • 把系统盘变成动态磁盘了!(记一次作死导致重装系统的经历)
    记一次作死导致重装系统的经历环境系统:win11型号:HP战99商用台式机参数:i5/RTX3060/12g/32g/1T固态+2T机械起因我的系统盘容量1T,之前分出256G为D盘,用来管理软件、代码等,但今天突然觉得256G有点少,故想扩展D盘,但是扩展按钮为灰色。结果一不小心栽在了一个CSDN的老哥手上,三步操......
  • 【旧文重发】MATLAB 通过函数封装一劳永逸地解决线性规划与运输问题的linprog的标准化
    这篇随笔原本是我上实验课时候的笔记,2023年7月曾经在CSDN平台上发布过。今天恰好有朋友跟我问起MATLAB自带的求解器输入很不直观的问题,我打开这个文章发给他的时候发现自己一年前写的LaTeX公式依托答辩,所以重打了一遍。再加上由于CSDN平台的持续摆烂,终于是用不下去......
  • Linux运行时动态库搜索路径优先级
    Windows运行时动态库搜索路径优先级:在Windows运行时,动态库(通常指DLL文件)的搜索路径遵循一定的优先级顺序,以确保程序能够正确地加载所需的动态库。以下是对Windows运行时动态库搜索路径优先级的总结:应用程序所在的目录:当一个应用程序(如exe文件)尝试加载一个DLL时,它首先会在自......
  • 在SQL Server中,可以使用查询结果生成SQL语句,通常通过动态SQL来实现。以下是一些常见的
    ai查到的,用着可以的,记录下示例场景假设有一个名为Employees的表,包含EmployeeID、FirstName和LastName字段。我们想要根据查询结果生成一系列的INSERT语句。1.使用FORXMLPATH生成INSERT语句SELECT'INSERTINTOEmployees(EmployeeID,FirstName,LastName)VALUES(......
  • KnowDLLs 是一个工具,旨在帮助用户识别和管理系统中的动态链接库(DLL)文件。它可以用于检
    WinObj是一个用于查看和分析Windows操作系统对象和对象命名空间的工具,主要用于系统调试和安全分析。它可以帮助用户了解系统中的各种对象,包括文件、进程和注册表项。KnowDLLs则是一个具体的工具,旨在识别和管理系统中的动态链接库(DLL),帮助用户检测潜在的恶意或不必要的DLL文件......
  • ABAP动态内表
    需求,内表值对比已知一方数据来源于外围系统,另一方数据来源于SAP。经过处理得到多个两两比较的内表,现在通过指针,指向两个相同的数据TYPES:BEGINOFl_person,pernrTYPEpernr_d,ENDOFl_person.TYPES:t_personTYPETABLEOFl_person.DATA:lt_person......