【算法】数位 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 ...
随机推荐
- easyui datebox 年月 yyyyMM 格式
//js日期重写ny为 function formatTime(ny){ var p = ny.datebox('panel'), //日期选择对象 tds = false, //日期选择对象中月份 ...
- 简述java多态
一.多态性: 1.java实现多态的前提:继承.覆写: 2.覆写调用的前提:看new是哪个类的对象,而后看方法是否被子类覆写,若覆写则调用覆写的方法,若没覆写则调用父类的方法: 二.多态性组成: 1方 ...
- 事件驱动之JDK观察者模式
JDK中关于观察者模式主要了解俩个概念 Observer观察者 Observable事件源:当事件源发生某事件时,有两个事情需要注意 1.里面有一个isChange属性 当为false时不会发通知给 ...
- 入门oj 6451: The XOR Largest Pair之二
Description 今天小W用了1s不到的时候完成了这样一个题:在给定的N个整数 A_1,A_2,-,A_N中选出两个进行异或运算,得到的结果最大是多少?正当他志得意满时,L老师亮出了另一个题:给 ...
- idea中maven的安装与配置
说明:类似maven安装和配置的帖子在网上有很多,本人也有做过参照,但是有些帖子的步骤跳跃性比较大,故此,本人整理了一下,给大家做个参考. 一.下载安装 一般都是在官网进行下载 https://mav ...
- 洛谷P1055 字符串的处理-----ISBN
题目描述 每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包括99位数字.11位识别码和33位分隔符,其规定格式如x-xxx-xxxxx-x,其中符号-就是分隔符(键盘上的减号),最后一位 ...
- 强大生产力工具Alfred
今天要给大家介绍的工具是Alfred,一款Mac下的高效生产力产品.它能做什么呢?简单的说就是:让你能够通过打几个字,就可以完成原本需要一顿操作的事情.举一个简单的栗子:如果我们要在Google搜索一 ...
- 【Redis3.0.x】配置文件
Redis3.0.x 配置文件 概述 Redis 的配置文件位于Redis安装目录下,文件名为 redis.conf. 可以通过 CONFIG 命令查看或设置配置项. Redis 命令不区分大小写. ...
- self-taught CS resouce recommendation
https://github.com/keithnull/TeachYourselfCS-CN/blob/master/TeachYourselfCS-CN.md#%E8%AE%A1%E7%AE%97 ...
- Java 使用 commons-fileupload 实现文件上传工具类
依赖包 文件上传可以使用 Apache 文件上传组件, commons-fileupload, 它依赖于 commons-io commons-io.jar: https://repo1.maven. ...