【动态规划】数位DP的原理、模板(封装类) - 教程

news/2025/11/11 8:09:32/文章来源:https://www.cnblogs.com/gccbuaa/p/19208782

本文涉及知识点

C++动态规划

复杂但相对容易理解的解法

上界、下界的位数一样都为N。如果不一样,拆分一样。比如:[10,200],拆分[10,99]和[100,200]。由于要枚举到1∼N1\sim N1N,故实际复杂度是N倍。

动态规划的状态表示

dp[n][m][m1],n表示已经处理最高n位,m表示上下界状态:0非上下界,1下界,2上界,3上下界。m1是自定义状态。
某题范围是[110,190],处理一位后:1是上下界,无其它合法状态。处理二位后,11是下界,19是上界,12∼1812 \sim 181218是非上下界。
空间复杂度:O(4N4N4N)

动态规划的状态表示

第一层循环n从小到大;第二层循环m,任意顺序;第三层自定义状态。

动态规划的转移方程

前n位状态当前位状态前n+1位状态
上下界上下界上下界
上界上界
下界下界
下界上界之间非界
上界上界上界
上界<上界非界
下界下界下界
下界>下届非界
非界任意非界

单个状态的时间复杂度:∑\sum。总时间复杂度:O(4N ∑\sum)

动态规划的初始值

枚举最高位。

动态的返回值

dp.back() 和自定义状态有关。

优化一

如果上下界没有公共前缀,则不存在状态上下界。
如果公共前缀为len,则目标串的前n位必定和上下界相同。故可以不处理0∼n−10 \sim n-10n1,直接从第n位开始处理。

优化二

[left∼r]的方案数=[0∼r]的方案数−[0∼left−1]的方案数=[0∼r]的方案数−[0∼left]的方案数+left是否符合[left \sim r]的方案数 =[0 \sim r]的方案数-[0\sim left-1]的方案数=[0 \sim r]的方案数-[0\sim left]的方案数 + left是否符合[leftr]的方案数=[0r]的方案数[0left1]的方案数=[0r]的方案数[0left]的方案数+left是否符合。简化后只需要考虑是否是上限。

优化三

小于N位的数,可以可以看成前导0。

自己摸索的封装类

template<class ELE, class ResultType, ELE minEle, ELE maxEle>class CLowUperr{public:CLowUperr(int iCustomStatusCount) :m_iCustomStatusCount(iCustomStatusCount){}void Init(const ELE* pLower, const ELE* pHigh, int iEleCount){m_vPre.assign(4, vector<ResultType>(m_iCustomStatusCount));if (iEleCount <= 0){return;}InitPre(pLower, pHigh);iEleCount--;while (iEleCount--){pLower++;pHigh++;vector<vector<ResultType>> dp(4, vector<ResultType>(m_iCustomStatusCount));OnInitDP(dp);//处理非边界for (auto tmp = minEle; tmp <= maxEle; tmp++){OnEnumOtherBit(dp[0], m_vPre[0], tmp);}//处理下边界OnEnumOtherBit(dp[1], m_vPre[1], *pLower);for (auto tmp = *pLower + 1; tmp <= maxEle; tmp++){OnEnumOtherBit(dp[0], m_vPre[1], tmp);}//处理上边界OnEnumOtherBit(dp[2], m_vPre[2], *pHigh);for (auto tmp = minEle; tmp < *pHigh; tmp++){OnEnumOtherBit(dp[0], m_vPre[2], tmp);}//处理上下边界if (*pLower == *pHigh){OnEnumOtherBit(dp[3], m_vPre[3], *pLower);}else{OnEnumOtherBit(dp[1], m_vPre[3], *pLower);for (auto tmp = *pLower + 1; tmp < *pHigh; tmp++){OnEnumOtherBit(dp[0], m_vPre[3], tmp);}OnEnumOtherBit(dp[2], m_vPre[3], *pHigh);}m_vPre.swap(dp);}}ResultType Sum(int iMinMask,int iMaxMask)const{ResultType iRet = 0;for (int status = 0; status < 4; status++){for (int mask = iMinMask; mask <= iMaxMask; mask++){iRet += m_vPre[status][mask];}}return iRet;}protected:const int m_iCustomStatusCount;virtual void OnEnumOtherBit(vector<ResultType>& dp, const vector<ResultType>& vPre, ELE curValue) = 0;virtual void OnEnumFirstBit(vector<ResultType>& vPre, const ELE curValue) = 0;virtual void OnInitDP(vector<vector<ResultType>>& dp){}vector<vector<ResultType>> m_vPre;private:void InitPre(const ELE* const pLower, const ELE* const pHigh){for (ELE cur = *pLower; cur <= *pHigh; cur++){int iStatus = 0;if (*pLower == cur){iStatus = *pLower == *pHigh ? 3 : 1;}else if (*pHigh == cur){iStatus = 2;}OnEnumFirstBit(m_vPre[iStatus], cur);}}};

参考通用模板:动态规划之记忆化搜索

Rec(i,bUpper)
返回值:bUpper,当前数据的前i位和上界是否相同。有两种解释:一,前i位自定义状态为m的方案数。二,后N-i位自定义状态m的方案数。大部分题,两种解释都行得通。
dp[i] = Rec(i,false)。
任意Rec(i,true) 只会被调用一次,故无需缓存。
令上界是数字n,N = logn,即n的位。
状态数:N。 每个状态的时间复杂度O(∑\sum)。故总复杂度:O(∑\sumN)。1∼N−11 \sim N-11N1位可以直接通过dp计算,故处理1∼N−11 \sim N-11N1的时间可以忽略。

回调类

template<class ELE, class ResultType>class IUpperDPCall{public:virtual void OnEnum(int n,vector<ResultType>& dp, const vector<ResultType>& vNext, ELE curValue, int iCustomStatusCount) = 0;virtual void OnInitEnd(vector<ResultType>& dp, vector<ResultType>& upDp) = 0;};

OnInitEnd:
dp= m_vDP[n]和dpUp=m_vDpUpper[n]。dp[m]和dpUp[m]表示自定义状态为m的方案数。
OnEnum有以下三种情况,三者的逻辑是一样的,故实现时无需考虑是那种情况。
情况一:dp=m_vDP[n],vNext=m_vDP[n+1]
情况二:dp=m_vDpUpper[n],vNext=m_vDpUpper[n+1]
情况三:dp=m_vDpUpper[n],vNext=m_vDP[n+1]
curValue当前元素的值。
iCustomStatusCount 自定义状态的数量。

最高位的取值范围和其它位相同

比如:枚举字母,容许前导0,即N-1位可以看成有一个前导0的N位数。

template<class ELE, class ResultType>class CUperrDP{public:CUperrDP(int iCustomStatusCount, IUpperDPCall<ELE, ResultType>& call, ELE minEle, ELE maxEle):m_iCustomStatusCount(iCustomStatusCount),m_call(call),m_minEle(minEle),m_maxEle(maxEle){}void Init(const ELE* pHigh, int iEleCount){m_vDP.assign(iEleCount + 1, vector<ResultType>(m_iCustomStatusCount));m_vDpUpper = m_vDP;m_call.OnInitEnd(m_vDP.back(), m_vDpUpper.back());//预处理增加的一位for (int i = iEleCount - 1;i > 0;i--) {m_call.OnEnum(i,m_vDpUpper[i], m_vDpUpper[i + 1], pHigh[i],m_iCustomStatusCount);for (auto j = m_minEle; j < pHigh[i];j++) {m_call.OnEnum(i,m_vDpUpper[i], m_vDP[i + 1], j, m_iCustomStatusCount);}for (auto j = m_minEle; j <= m_maxEle;j++) {m_call.OnEnum(i,m_vDP[i], m_vDP[i + 1], j, m_iCustomStatusCount);}}m_call.OnEnum(0,m_vDpUpper[0], m_vDpUpper[1], pHigh[0], m_iCustomStatusCount);for (auto j = m_minEle; j < pHigh[0];j++) {m_call.OnEnum(0,m_vDP[0], m_vDP[1], j, m_iCustomStatusCount);}}ResultType Sum(int iMinCustomStatu, int iMaxCustomStatu) {ResultType ret = 0;for (int i = iMinCustomStatu; i <= iMaxCustomStatu;i++) {ret += m_vDP[0][i] + m_vDpUpper[0][i];}return ret;}ResultType Sum() {return Sum(0, m_iCustomStatusCount - 1);}vector<vector<ResultType>> m_vDP, m_vDpUpper;const ELE m_minEle, m_maxEle;protected:const int m_iCustomStatusCount;IUpperDPCall<ELE, ResultType>& m_call;};

ELE:元素的类型,几乎全部是char。
ResultType:记录方案数量的类型,如果:int,long long,自定义数据类型。
minEle:最小元素
maxEle:最大元素。
pHigh, int iEleCount:上限字符串的数量和长度。
Init的大致逻辑:
一,初始化。
二,n = N-1 to 1。如果当前元素等于pHigh[n],dpUp[n]对应dpUp[n+1];如果当前元素小于pHigh[n],dpUp[n]对应dpUp[n+1];任意当前元素,dp[n]对应dp[n+1]。
三,第0个元素等于pHigh[0],则dpUp[0]对应dpUp[1]。第0个元素小于pHigh[0],则dp[0]对应dp[1]。

最高位的取值范围和其它位不同

如:正整数不能以0开始。原理类似上一部分,只描述不同点:
firstMinEle,最高位最小元素。
m_vFirstDP[n][m]:N-n位数,自定义状态为m的方案数。 m_vFirstDP[N]未使用。

template<class ELE, class ResultType>class CUperrDP2{public:CUperrDP2(int iCustomStatusCount, IUpperDPCall<ELE, ResultType>& call, ELE minEle, ELE maxEle, ELE firstMinEle):m_iCustomStatusCount(iCustomStatusCount), m_call(call), m_minEle(minEle),m_maxEle(maxEle),m_firstMinEle(firstMinEle){}void Init(const ELE* pHigh, int iEleCount){m_vDP.assign(iEleCount + 1, vector<ResultType>(m_iCustomStatusCount));m_vDpUpper = m_vDP;m_vFirstDP = m_vDP;m_call.OnInitEnd(m_vDP.back(), m_vDpUpper.back());//预处理增加的一位for (int i = iEleCount - 1;i > 0;i--) {m_call.OnEnum(i,m_vDpUpper[i], m_vDpUpper[i + 1], pHigh[i], m_iCustomStatusCount);for (auto j = m_minEle; j < pHigh[i];j++) {m_call.OnEnum(i,m_vDpUpper[i], m_vDP[i + 1], j, m_iCustomStatusCount);}for (auto j = m_minEle; j <= m_maxEle;j++) {m_call.OnEnum(i,m_vDP[i], m_vDP[i + 1], j, m_iCustomStatusCount);}for (auto j = m_firstMinEle; j <= m_maxEle;j++) {m_call.OnEnum(i,m_vFirstDP[i], m_vDP[i + 1], j, m_iCustomStatusCount);}}m_call.OnEnum(0,m_vFirstDP[0], m_vDpUpper[1], pHigh[0], m_iCustomStatusCount);for (auto j = m_firstMinEle; j < pHigh[0];j++) {m_call.OnEnum(0,m_vFirstDP[0], m_vDP[1], j, m_iCustomStatusCount);}}ResultType Sum(int iMinCustomStatu, int iMaxCustomStatu) {ResultType ret = 0;for (int i = 0;i + 1 < m_vFirstDP.size();i++) {ret += accumulate(m_vFirstDP[i].begin() + iMinCustomStatu, m_vFirstDP[i].begin() + iMaxCustomStatu + 1, (ResultType)0);}return ret;}ResultType Sum() {return Sum(0, m_iCustomStatusCount - 1);}vector<vector<ResultType>> m_vDP, m_vDpUpper,m_vFirstDP;ELE m_minEle, m_maxEle, m_firstMinEle;protected:const int m_iCustomStatusCount;IUpperDPCall<ELE, ResultType>& m_call;};

样例

力扣

难度分
【二分查找 数位DP】:902最大为 N 的数字组合1990
【C++动态规划 数位dp】2376. 统计特殊整数2120
【数位dp 动态规划 状态压缩】【推荐】1012. 至少有 1 位重复的数字2230
【数位dp】3519. 统计逐位非递减的整数2246
【动态规划 数位dp】2827. 范围中美丽整数的数目2324
【数位dp】【数论】【动态规划】2999. 统计强大整数的数目2351
【C++算法 数位dp 动态规划】2719统计整数数目2355
【C++动态规划】2801. 统计范围内的步进数字数目2367
【数位dp】3704. 统计和为 N 的无零数对2419
【逆向思考 数位dp】3352. 统计小于 N 的 K 可约简整数2451
【动态规划 数位dp】3490. 统计美丽整数的数目2502
【数位dp KMP】1397. 找到所有好字符串2667
【C++ 数位dp】600. 不含连续1的非负整数无分数

洛谷

难度等级
【数学 进制 数位DP】P9362 [ICPC 2022 Xi‘an R] Find Maximum普及+

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

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

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

相关文章

在JMeter中利用地名查询天气预报并查看响应结果

1. 新建线程组:打开JMeter,在测试计划上右键单击,选择“添加”→“线程组”,并为其命名,如“天气查询线程组”。 2. 添加HTTP请求获取城市代码:在线程组上右键单击,选择“添加”→“取样器”→“HTTP请求”,命…

2025年靠谱的主动边坡防护网最新TOP品牌厂家排行

2025年靠谱的主动边坡防护网最新TOP品牌厂家排行行业背景与市场趋势随着我国基础设施建设的持续投入和地质灾害防治意识的提升,边坡防护网行业迎来了快速发展期。据中国地质灾害防治工程行业协会最新数据显示,2024年…

2025年质量好的再生法兰绒行业内口碑厂家排行榜

2025年质量好的再生法兰绒行业内口碑厂家排行榜行业背景与市场趋势再生法兰绒作为环保纺织材料的重要组成部分,近年来在全球纺织行业获得了快速发展。根据中国纺织工业联合会最新发布的《2024-2025中国纺织行业发展趋…

biji-mysql

一、基础 1)基本概念 2)MySQL 支持的数据类型 3)MySQL 中的运算符 4)MySQL 常用函数 5)SQL 6)约束 7)多表查询 8)其它知识点 一、基本概念 1.数据库:存储数据的仓库,本质上是一个文件系统,将数据按照特定的…

2025年11月小户型油烟机型号排名榜:小厨房净烟方案实测

把厨房塞进8㎡以内,油烟却不想“蜗居”。开火三分钟,客厅就飘起葱蒜味;煎条鱼,橱柜门第二天就黏手——这是北京、上海、深圳等城市精装公寓住户的普遍痛点。住建部《2024住宅户型趋势报告》显示,全国60%新增住宅套…

哔哩哔哩网页端查看账号已注销视频

哔哩哔哩网页端查看账号已注销视频链接:https://www.bilibili.com/list/+uid如:https://www.bilibili.com/list/1606577968

DELPHI 构造函数传参

需求: 窗体A 调用窗体B ,窗体B又调用 FrameC.现在要把A中的一个值 S 传给FrameC.然后执行P过程.P在执行的过程中,需要用到P值. 我最开始的设想大概如下(这是不对的):窗体Aprocedure TMaterial2.ModifyInformationCl…

在AI技术快速实现创意的时代,挖掘数学学习新需求成为关键挑战

该项目是一个精心整理的数学资源集合,涵盖从基础数学到高级分支的各类学习资料,包括学习平台、工具软件、教材讲义和视频课程等,为数学学习者提供全面系统的学习支持。a.内容描述核心功能定位:该项目是一个精心整理…

2025 年 11 月冷库板/聚氨酯冷库板/机制板, 冷库门, 冷库工程/冷库集成厂家推荐排行榜,专业实力与客户满意度全景解析及选购指南

2025 年 11 月冷库板/聚氨酯冷库板/机制板, 冷库门, 冷库工程/冷库集成厂家推荐排行榜,专业实力与客户满意度全景解析及选购指南 随着冷链物流行业的快速发展,冷库板、聚氨酯冷库板、机制板、冷库门以及冷库工程和冷…

云调试

云调试https://www.zyyyun.com/ 下载cc 打开网络调试 打开aya 可以运行frida但是提示 frida -U -p 752 -l "c:\Users\21558\Videos\shj\analysis\hook_shanhaiching.js" ____ / _ | Frida 17.4.4 …

文件上传使用分片,文件下载使用流式方案,同时还要实现进度条功能

好的,这是一个非常经典和完整的文件传输方案。我们将采用业界公认的最佳实践:文件上传:前端分片 + 后端记录与合并,实现进度条和断点续传。 文件下载:后端流式响应 + 前端读取流,实现平滑进度条。 这套组合拳能完…

HeavenHell

Heaven&Hell只要是我能做的,我什么都愿意做。香樟枯叶有淡淡的铁锈味。然后我感觉高兴。我不喜欢甜腻的薄荷的香味,我喜欢好闻的味道。 一事无成,我想是的。我甚至失去死在十八岁前夜的勇气。我越来越冗长了。 …

Why can people actually only speak one language

its the language of themselves. USA CHN, UK are all bad.

基于MATLAB的3-PUU并联机构工作空间仿真分析

1.课题概述 3-PUU 并联机构是一种典型的空间并联机构,在机器人、精密定位设备等诸多领域有着广泛的应用。对其工作空间进行准确分析,有助于了解机构的运动范围和性能,从而为机构的设计、控制和应用提供重要依据。 2…

基于氢氧燃料电池的分布式三相电力系统Simulink建模与仿真

1.课题概述 基于氢氧燃料电池的分布式三相电力系统Simulink建模与仿真,仿真输出燃料电池中氢氧元素含量变化以及生成的H2O变化情况。 2.系统仿真结果3.核心程序与模型 版本:MATLAB2022a4.系统原理简介 氢氧燃料电池原…

sunk cost

If there were no utopia or paradise in the world all we need is just sunk cost. although somehow I indeed speak Chinese unwillingly.

英皇热水器售后服务电话4009968065

英皇热水器推出24小时售后客服受理中心(2025更新上线) 英皇热水器专业售后维修服务电话:4009968065让温暖无忧回归 冬季来临,英皇热水器作为家庭温暖的守护者,其稳定运行至关重要。但长期使用后各种问题可能导致机器…

巧夫人油烟机售后服务电话4009968065

巧夫人油烟机推出24小时售后客服受理中心(2025更新上线) 巧夫人油烟机专业售后维修服务电话:4009968065让温暖无忧回归 冬季来临,巧夫人油烟机作为家庭温暖的守护者,其稳定运行至关重要。但长期使用后各种问题可能导…

爱多集成灶售后服务电话4009968065

爱多集成灶推出24小时售后客服受理中心(2025更新上线) 爱多集成灶专业售后维修服务电话:4009968065让温暖无忧回归 冬季来临,爱多集成灶作为家庭温暖的守护者,其稳定运行至关重要。但长期使用后各种问题可能导致机器…

大宇mini壁挂洗衣机售后服务电话4009968065

大宇mini壁挂洗衣机推出24小时售后客服受理中心(2025更新上线) 大宇mini壁挂洗衣机专业售后维修服务电话:4009968065让温暖无忧回归 冬季来临,大宇mini壁挂洗衣机作为家庭温暖的守护者,其稳定运行至关重要。但长期使…