【算法】数位 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 ...
随机推荐
- 任意文件上传——tcp
package chaoba; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; ...
- JVM参数调优总结
一.前言 要想成为一名高级Java开发具备JVM调优的能力必不可少,能够根据项目实际情况进行JVM调优的前提是理解JVM原理和常用JVM参数的含义及作用,虽然<深入理解Java虚拟机>这本 ...
- 关于char是否能表示一个中文
char是可以表示中文的 这个问题点有3个考核点 1 char是多少位的 2 java用的是什么方式表示字符 3 Unicode是用多少位表示的 1的答案是16位的,2的答案是Unicode,3的答案 ...
- 9条消除if...else的锦囊妙计,助你写出更优雅的代码
前言 最近在做代码重构,发现了很多代码的烂味道.其他的不多说,今天主要说说那些又臭又长的if...else要如何重构. 在介绍更更优雅的编程之前,让我们一起回顾一下,不好的if...else代码 一. ...
- java中string、stringBuild、stringBuffer的区别
(1)string 1,Stirng是对象不是基本数据类型 2,String是final类,不能被继承.是不可变对象,一旦创建,就不能修改它的值. 3,对于已经存在的Sti ...
- Label_strange_labels
空格符号 特殊符号名 注释语句 , align = "left right center" 粗体 斜体 也可能是控制标签 上标 下标 大字号 小字号 下划线 删除线 等宽 键盘输 ...
- 写给小白看的Mysql事务
1 为什么需要事务 在网上的很多资料里,其实没有很好的解释为什么我们需要事务.其实我们去学习一个东西之前,还是应该了解清楚这个东西为什么有用,硬生生的去记住事务的ACID特性.各种隔离级别个人认为没有 ...
- Redis核心原理-简单动态字符串SDS
SDS简介 Redis是C语言编写的,但没有使用c语言的字符串结构,而是自己实现了一套简单动态字符串 simple dynamic string 简称SDS,SDS兼容C语言的字符串类型,原理类似Ja ...
- .netcore利用perf分析高cpu使用率
目录 一 在宿主机运行perf 二 容器内安装perf 1,重新构建镜像 2,下载火焰图生成脚本 3,安装linux-perf 三 CPU占用分析 1,perf record捕获进程 2,生成火焰图 ...
- Linux 中软链接和硬链接的使用
Linux 链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link). 硬链接和软链接 硬链接 --- ln 要链接的文件 新硬链接名 软连接 --- l ...