【算法】数位 dp
时隔多日,我终于再次开始写博客了!!
上午听了数位 dp,感觉没听懂,于是在网上进行一番愉 ♂ 快 ♀ 的学习后,写篇博来加深一下印象~~
前置的没用的知识
数位
不同计数单位,按照一定顺序排列,它们所占位置叫做数位。
在整数中的数位是从右往左,逐渐变大:第一位是个位,第二位是十位,第三位是百位,第四位是千位,第五位是万位,第六位是十万位,第七位是百万位,第八位是千万位,以此类推。
同一个数字,由于所在数位不同,计数单位不同,所表示数值也就不同。
对于每一个数都应当有一个名称,以自然数来说,自然数是无限多的,如果每一个自然数都用一个独立的名称来读出它,这是非常不方便的,也是不可能做到的。
为了解决这个问题,人们创造出一种计数制度,就是现在我们使用的十进制计数法。
———— 360 百科
感谢 360 百科朋友的友情兹瓷
顾名思义,数位 dp 就是在数位上的 dp。
主要应用
经常用来解决计数类的、范围很大的题目,
当然通常会有一些条件。
特点
很板很板,几乎跟背包 dp 一样一个板子走天下,
用句话来误导人一下:记个板子然后不用理解都行……
正题
先放个例题
不含前导零且相邻两个数字之差至少为 \(2\) 的正整数被称为 \(\text{windy}\) 数。
windy 想知道,在 \(a\) 和 \(b\) 之间,包括 \(a\) 和 \(b\) ,总共有多少个 \(\text{windy}\) 数?
范围:\(a,b\in [1,2\times10^{9}]\)
我相信,如果没有那个巨大的范围的话,正常人都能做得出来 = =
但这个范围就神奇……灭掉了一群正常人……
总不能从 \(1\) 一直枚举到 \(10^{9}\) 吧……
这时候就是 超级飞侠 数位 dp 出现的时候了!!
分析
我们可以想一下:\(2\times10^{9}\) 一共就只有 \(9\) 位,毫无疑问的是,如果针对这个数的每一位开一个数组,最多就只开 \(9\) 个。
然后,再想一下:
如果在每一位枚举 \(0\) 到 \(9\),那时间复杂度肯定不会炸掉。
如果规定了一个数的最高位,那么剩下的位数有多少种组合,然后再规定剩下的数的最高位,然后再……之后这个数就被彻底地拆分了~~
结论
我们可以用万能 yuechi ———— 大法师直接打过去!!
然后就 T 了~~
所以用记搜啊~~~(本蒟蒻太弱了,不会递推)
代码实现
设一个 \(f[N][1]\) 来记录状态,其中的 \(f[i][j]\) 表示位数最高位为 \(i\),该位数的上一个数为 \(j\) 。
每搜一个数位,枚举这个数位的数字(从 \(0\) 到 \(9\)),然后判断这个数合不合法(比如这个例题就是判断是否与上个数之差不小于 \(2\))。
如果合法,就继续搜下一个数位;
如果不合法,就剪枝。
判断边界:
判断最高位时看看有没有到最大值,然后一位一位的推。
举个栗子:枚举 \(7456\) 的时候最高位先枚举 \([0,6]\),等以 \([0,6]\) 开头的数都被判断完了,就使 Qd (判断的变量)变成一,然后一一排查千位不能超过 \(7\),百位不能超过 \(4\),十位不能超过 \(5\),个位不能超过 \(6\)等等……
这时候大约轮廓就出来了:(以这个题为例)
| 变量名 | 意义 |
|---|---|
| Ws | 位数 |
| pre | 前一个数 |
| Qd1 | 判断是否有前导零 |
| Qd2 | 判断是否到了边界 |
| top | 枚举的数不能超过 top |
| ans | 记录答案 |
int dp(int Ws, int pre, bool Qd1, bool Qd2) {
if (!Ws) return 1; // 如果位数减到了 0,说明之前的数都是合法的,返回 1 。
if (!Qd1 && !Qd2 && f[Ws][pre] != -1) return f[Ws][pre]; // 如果没有前导零并且未到达边界,且记忆数组 f 不为空,就返回 f 。
int top = Qd2 ? a[Ws] : 9, ans = 0; // 判断上限
for (int i = 0; i <= top; ++i) { // 枚举数
if (abs(pre - i) < 2 && !Qd1) continue; // 不合法,剪枝
ans += (i || !Qd1) ? dp(Ws - 1, i, 0, Qd2 && i == a[Ws]) : dp(Ws - 1, i, 1, Qd2 && i == a[Ws]); // 判断是否有前导零,若有,则使 Qd2 变成 1 ,如果到达边界,则使下一个数的 Qd2 变成 1 。
}
if (!Qd1 && !Qd2) f[Ws][pre] = ans; // 记忆 ans 。
return ans;
}
我相信到现在还是有一些疑问的,比如,为什么要先判断 Qd1 和 Qd2 后才能进行记忆化,才能返回 f 值。
解释一下
我们可以想象,\(f[i][j]\) 数组记录的是第 \(i\) 位数,前一个数为 \(j\) 的数的方案数,
那么如果这个数组已经在还未到达边界,还未有前导零的时候已经被更新过了,这时候递归到边界, \(f[i][j]\) 记录的是后面的数从 \([0,9]\) 的方案数,但边界不一定能取到 \([0,9]\),所以不判边界和前导零显然是不对的。
再放个例题
给定两个正整数 \(a\) 和 \(b\),求在 \([a,b]\) 中的所有整数中,每个数码各出现了多少次。
范围:\(a,b\in[1,10^{12}]\)
当你看到这个数据范围的时候,毫无疑问这绝对又是个数位 dp 题,
分析
与上一题一样,但条件变成计数了(每个数码有多少个)。
代码实现
设一个 \(f[N][20]\) 来记录状态,其中的 \(f[i][j]\) 表示位数最高位为 \(i\),该数码在这个数中出现了几次为 \(j\) 。
然后跟上一题一样。
判断边界:
跟上一题一样,而且还是要判断前导零(毕竟不能把前导零计入 \(0\) 的计数中)。
这时候代码就出来了:(以这个题为例)
| 变量名 | 意义 |
|---|---|
| Ws | 位数 |
| Qd1 | 判断是否有前导零 |
| Qd2 | 判断是否到了边界 |
| shu | 数码 |
| sum | 记录该数码出现多少次 |
| top | 枚举的数不能超过 top |
| ans | 记录答案 |
int dp(int Ws, bool Qd1, bool Qd2, int sum) {
if (!Ws) return sum;
if (!Qd1 && !Qd2 && f[Ws][sum] != -1) return f[Ws][sum];
int top = Qd2 ? a[Ws] : 9, ans = 0;
for (int i = 0; i <= top; ++i)
ans += dp(Ws - 1, !(i || !Qd1), Qd2 && i == a[Ws], sum + ((i == shu) && (i || !Qd1)));
// 这里解释一下,当 i 为该数码时,判断该数是否为前导零。
if (!Qd1 && !Qd2) f[Ws][sum] = ans;
return ans;
}
现在应该能看出来了,数位 dp 什么的,就是个板子(对于记搜来说)~~
例题
洛谷 P2657 [SCOI2009] windy 数(上面第一道例题)
洛谷 P2602 [ZJOI2010]数字计数(上面第二道例题)
剩下的自己搜~~
【算法】数位 dp的更多相关文章
- 算法-数位dp
算法-数位dp 前置知识: \(\texttt{dp}\) \(\texttt{Dfs}\) 参考文献 https://www.cnblogs.com/y2823774827y/p/10301145. ...
- 算法笔记--数位dp
算法笔记 这个博客写的不错:http://blog.csdn.net/wust_zzwh/article/details/52100392 数位dp的精髓是不同情况下sta变量的设置. 模板: ]; ...
- 牛客寒假算法基础集训营3处女座和小姐姐(三) (数位dp)
链接:https://ac.nowcoder.com/acm/contest/329/G来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...
- 算法复习——数位dp
开头由于不知道讲啥依然搬讲义 对于引入的这个问题,讲义里已经很清楚了,我更喜欢用那个建树的理解···· 相当于先预处理f,然后从起点开始在树上走··记录目前已经找到了多少个满足题意的数k,如果枚举到第 ...
- 算法复习——数位dp(不要62HUD2089)
题目 题目描述 杭州人称那些傻乎乎粘嗒嗒的人为 62(音:laoer). 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司 ...
- bzoj 1026: [SCOI2009]windy数 & 数位DP算法笔记
数位DP入门题之一 也是我所做的第一道数位DP题目 (其实很久以前就遇到过 感觉实现太难没写) 数位DP题目貌似多半是问从L到R内有多少个数满足某些限制条件 只要出题人不刻意去卡多一个$log$什么的 ...
- 「算法笔记」数位 DP
一.关于数位 dp 有时候我们会遇到某类问题,它所统计的对象具有某些性质,答案在限制/贡献上与统计对象的数位之间有着密切的关系,有可能是数位之间联系的形式,也有可能是数位之间相互独立的形式.(如求满足 ...
- 浅谈数位DP
在了解数位dp之前,先来看一个问题: 例1.求a~b中不包含49的数的个数. 0 < a.b < 2*10^9 注意到n的数据范围非常大,暴力求解是不可能的,考虑dp,如果直接记录下数字, ...
- 数位dp/记忆化搜索
一.引例 #1033 : 交错和 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0, a1, ..., an ...
随机推荐
- Ubuntu安装最新的SlickEdit软件--破解教程
最近主要工作系统转到LInux上面来了,Slickedit的安装破解也费了些事,今天将过程整理一下做个记录. 说明:SlickEdit pro V21.03 Linux 64位实测可用,MAC实测可用 ...
- Spring IOC 笔记
什么是IOC与DI IOC(inversion of control) 它描述的其实是一种面向对象编程中的设计原则,用来降低代码之间的耦合度, 而DI(dependency Injection)依赖注 ...
- 数据库分库分表容量划分建议参考阿里云DRDS原则
做分库分表的时候 一直想知道分库分表容量的最优规则有什么好的建议,以下是参考阿里云 DRDS 分库分表的规则,还是有一定的参考意义 .
- IntelliJ IDEA实用插件
Free MyBatis plugin 插件效果 Save Actions 插件设置 勾选后Ctrl + S就会执行格式化操作,等价于格式化快捷键Alt + Ctrl + L
- nodejs事件和事件循环详解
目录 简介 nodejs中的事件循环 phase详解 timers pending callbacks idle, prepare poll轮询 check close callbacks setTi ...
- 借助Docker搭建JMeter+Grafana+Influxdb监控平台
我们都知道Jmeter提供了原生的结果查看,既然有原生的查看结果,为什么还要多此一举使用其他工具进行查看呢,除了查看内容丰富外还有最主要的原因:Jmeter提供的查看结果插件本身是比较消耗性能的,所以 ...
- 【Java基础】面向对象中
面向对象中 这一章主要涉及面向对象的三大特征,包括封装.继承.多态.(抽象). 封装 程序设计追求"高内聚,低耦合": 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉: 低 ...
- 【Spring】Spring 入门
Spring 入门 文章源码 Spring 概述 Spring Spring 是分层的 Java SE/EE 应用全栈式轻量级开源框架,以 IOC(Inverse Of Control,反转控制)和 ...
- 号称能将STW干掉1ms的Java垃圾收集器ZGC到底是个什么东西?
ZGC介绍 ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的实验性质的垃圾收集器,它曾经设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的 ...
- MybatisPlus多数据源及事务解决思路
关于多数据源解决方案 目前在SpringBoot框架基础上多数据源的解决方案大多手动创建多个DataSource,后续方案有三: 继承org.springframework.jdbc.datasour ...