【算法】数位 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 ...
随机推荐
- [LeetCode]654. Maximum Binary Tree最大堆二叉树
每次找到数组中的最大值,然后递归的构建左右树 public TreeNode constructMaximumBinaryTree(int[] nums) { if (nums.length==0) ...
- Service Mesh——微服务中的流量管理中间件
Service Mesh--微服务中的流量管理中间件 摘自-https://zhuanlan.zhihu.com/p/28794062 Service mesh 与 Cloud Native Kube ...
- Java中定时器Timer致命缺点(附学习方法)
简介 这篇文章我一直在纠结到底要不要写,不想写一来因为定时器用法比较简单,二来是面试中也不常问.后来还是决定写了主要是想把自己分析问题思路分享给大家,让大家在学习过程中能够参考,学习态度我相信大部分人 ...
- centos7搭建sonarqube环境+jenkins部署全流程
一.简介sonarqube是一个用于代码质量管理的开源平台,用于管理源代码的质量 不遵循代码标准sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具规范代码编写.潜在的 ...
- 瞄到BindingGroup用法
文章转载于https://www.cnblogs.com/dangnianxiaoqingxin/p/12653988.html 2.BindingGroup的使用 public class MyCl ...
- ElasticSearch- 单节点 unassigned_shards 故障排查
故障现象 在部署ELK的单机环境,当连接Kibana时候提示下面错误,即使重启整个服务也是提示Kibana server is not ready. {"message":&quo ...
- 对于k8s微服务的性能测试监控平台搭建
之前有写过对于传统项目的性能测试监控,但是对于目前市场占比已经很低,大部分项目使用k8s,今天讲一下对于k8s如何去监控. 对于k8s的监控我们所有的操作都要在master下进行. 一.部署grafa ...
- 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定
2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...
- docker save 保存导出镜像
Docker保存镜像 tag 镜像 # 镜像打 tag 标签 # docker tag 镜像id/名 新名字 docker tag fce91102e17d tomcat01 commit 镜像 注意 ...
- Java 用java GUI写一个贪吃蛇小游戏
目录 主要用到 swing 包下的一些类 上代码 游戏启动类 游戏数据类 游戏面板类 代码地址 主要用到 swing 包下的一些类 JFrame 窗口类 JPanel 面板类 KeyListener ...