`算法知识` 数位DP

本文介绍了数位DP的概念,实际上是一种通过集合拆分和处理单个集合的算法,核心在于二叉树结构。数位二叉树将数字转换为特定问题,利用递归或排列组合在线性时间内求解集合的贡献。文章详细讨论了如何构建数位二叉树,解释了字符串数与数字的关系,并提供了处理不同集合的方法,包括如何处理前导零的特殊情况。
摘要由CSDN通过智能技术生成

数位DP

数位DP模型: 问[L -- R]这些(数字), 他们的贡献之和;

其实, 数位DP 这个名字, 并不能完全体现该算法; 因为, 该算法有时是可以不借助DP的, 而是借助(排列组合), 或直接暴力枚举;
该算法的核心, 在于: 将 这些数 (非常多) 拆分成 一些集合 (这些集合之和 等同 这些数), 一个集合里包含有很多数
然后单个集合, 通过(Dp 或 排列组合 或 暴力), 可以在(线性时间) 一次性的 求解出 该集合里所有数的贡献, 即该集合的贡献
因此, 核心在于 (集合拆分) + (处理单个集合的算法)
… 这个(集合拆分)的过程, 是一种 二叉树的过程; 因此, 我觉得, 数位DP 叫做: 数位二叉树, 更精确

数位二叉树


暴力求解, 就是FOR_( i, L, R, 1){ ans += 贡献( i);}

一般数据范围是: 0 <= L/R <= 10^n, 这个n, 可能是18(LL范围), 可能是100, 1000(比LL还大)
… 所以, 暴力肯定是不行的;


数位二叉树, 都会将 [L -- R]的答案 转换为: Prefix( R) - Prefix( L - 1)的问题;
其中Prefix( x)表示[0 -- x]范围的贡献; Prefix必须是从0开始, 即便L >= 1, 也要从0开始, 下面会谈到;

问题是: 如何求[0 -- x]的贡献呢?

TODO: photograph


有一点要说明, 图中的 (第一类的 第一集合, 即[0,00 -- 0,99]), 不能认为他是[0 -- 99], 这是不可以的!!! (当然我们的目标是让他代表[0 -- 99])
或者说, 该二叉树的拆分数集, 是把 x = ABC当成是一个(字符串), 然后拆分出来的所有集合, 都是 (字符串数)
… 解释下, 字符串数 与 数字 的概念; 这非常重要, 不仅在这里, 在接下来的数位DP递推, 也非常重要;
… 字符串数, 是可以带(前导零)的; 而数字 不可以带(前导零); 题目一般求解的都是(数字), 而数位DP求解的是(字符串数)
… 两者是可以对应的;
… 任意一个(数字), 一定对应(等价于) 无数个 (字符串数);
… … 比如, 数字x = 123, 则等价于: 字符串123, 0123, 00123, 000123, 0000123, ...
… 任意一个(字符串数), 一定对应(等价于) 一个 (数字);
… … 比如, ( 字符串数x = 00123, 则等价于: 数字123), ( 字符串数x = 0001, 则等价于: 数字1), ( 字符串数x = 00000, 则等价于: 数字0)

因为 (任何的字符串数, 一定对应 唯一的数字), (不带前导零的字符串数, 与 其数字, 完全相同), 则有:
数位二叉树中, 所有的集合 (即 字符串数的集合):
… 除了(第一类的第一集合), 所有集合, 都不带前导零; 所以, 其与 (数字) 是完全相同的
… … 比如, [A,1,0 -- A,1,9]这个集合 中 任意一个字符串数, 和 其对应的数字 完全相同; 因为不含(前导零);
… … 即对于"A13"这个字符串数, 你求他对答案的贡献, 和求123这个数字, 是完全一样的, 因为形态完全一样;
… 但是, (第一类的第一集合) [0,00 -- 0,99]这个字符串数集合, 全部都带有(前导零);
… … 你求 (001这字符串数 对答案贡献) 与 (1这个数字 对答案贡献), 两者就不同了; 因为形态就不同
… … (假如答案贡献是 判断是否含有相同数位, 001有两个0 含有相同数位; 而1是没有的)
… … 处理这个(带有前导零的)集合, 会通过继续分类的方式, 把前导零给消除掉, 下面会讲到;


按照上面的二叉树方式, 举个例子: x = 340
(第一类: [000 -- 099], [100 -- 199], [200 -- 299])
(第二类: [300 -- 309], [310 -- 319], [320 -- 329], [330 -- 339])
(第三类: 空)
(单独数: 340)

… 暂时把(第一类的第一集合) 认为是[0 -- 99], 即去掉前导零; 其他的集合, 都不含有前导零, 就把他转换为(数字);
… 即, 此时, 所有集合, 都代表(数字); (数位二叉树得到的是: 字符串数)

一共划分为 (3 + 4 + 0 = 7个集合) + (一个数340), 此时他们 等价于 [0 -- 340]

每一类 的 集合个数, 等价于: (该类对应的数位数值)
比如, 第一类对应3, 他就有3个集合; 第三类对应0, 他有0个集合


暂时不考虑: (第一类的第一集合) 和 (单独数), 看这些集合, 他们都形如: [pre,k个0 -- pre,k个9] (即一共是10^k个数)
其中, 如果把pre当作是个(数字), 则 pre 不含前导零, 且pre > 0; k = [0 -- n-1], 即, 这些集合, 表示[100 -- 339]这些数

即这个集合, 都是一个统一的形式: [pre, k个(自由数位)], 有一个固定前缀pre (所有数的前缀都相同)
后缀, 长度为k, 是从00 -- 99的递增, 我们称 这个后缀, 是由k个自由数位 组成的 , 一个自由数位 即[0 - 9]的任意
即, 一个固定前缀(pre), 后面有k个自由数位 的形式

每一个集合, 去调用一个Aligned( pre, k)的函数, 这个函数, 表示[pre,k个0 -- pre,k个9]这些 (字符串数) 的答案; (也就表示: 该集合的答案)

起名为Aligned, 就是表示(对齐), 表示, 这些数 都是位数相同的;
Aligned代表的是数字还是字符串数, 其实都一个意思;
因为, pre没有前导零pre > 0, 这两个条件 就会决定 此时, (他所代表的 (数字 与 字符串数)是一样的)

Aligned怎么实现, 暂且不论;


对于(单独数340), 直接写一个Single_number( x)函数, 特判;


关键是, 对于(第一类的 第一集合), 即[000 -- 099]这些字符串数;
他肯定不能像其他集合一样, 调用Aligned( {0}, 2), 因为字符串数001数字1 形态不相同;
因为我们的目的是: 让他去表示[0 -- 99], 将这个集合拆分为: [0 -- 0] [1 -- 9] [10 -- 99] [100 -- 999]
为啥要这样划分呢? 这样, 每个集合 都是(Aligned) 数位相同; 这个思想在数位DP里非常重要
… 特别的[0 -- 0] 他表示0这个数, 让他, 去调用Single_number( 0)
此时剩下[1 -- 9] [10 -- 99] … 每个集合, 都是Aligned. 但他不符合Aligned( pre, k)函数所要求的 形式
我们想办法, 让他变成去调用Aligned(pre, k)函数
对每个集合, 继续划分: [1 - 9] 划分为: [1-1] [2-2] ... [9-9]
[10 -- 99] 划分为: [10-19] [20-29] ... [90-99]
此时, 所有区间, 不仅是(对齐的), 而且, 符合 (一个固定前缀) (后面若干自由数位)的Aligned()函数形式;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值