【算法】数位 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 ...
随机推荐
- java动态代理实现与原理详细分析(转)
关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式 代理模式是常用的java设计模式, ...
- 微信支付(PC扫码支付和H5公众号支付)
最近在做微信支付,微信支付比较坑,官方居然只有.NET.C#.PHP的demo居然没有java的demo.然后微信支付是不提供测试账号的需要直接用正式的公众号.首先来介绍下微信扫码支付吧,微信扫码有两 ...
- MySQL全面瓦解15:视图
概述 很多时候,我们会有一些很复杂的数据库操作,比如整合用户的行为数据,那这些数据可能包含用户的餐饮.生活日用.充值消费.交通出行.通讯物流.交通出行.医疗保健.住房物业.运动健康... 基于此,我们 ...
- Solon 1.2.13 发布,开启与 Springboot 的互通
Solon 一个类似Springboot的微型开发框架.项目从2018年启动以来,参考过大量前人作品:历时两年,3500多次的commit:内核保持0.1m的身材,超高的Web跑分,良好的使用体验. ...
- LeapMotion控制器 java语言开发笔记--(连接控制器)
(1)为了连接LeapMotion控制器,创建controller对象,这个对象自动创建与控制器的服务或者后台程序连接,然后捕获数据提供给你的应用程序.可以使用该对象来获取连接信息. (2)获取Fra ...
- ASP.NET Core路由中间件[3]: 终结点(Endpoint)
到目前为止,ASP.NET Core提供了两种不同的路由解决方案.传统的路由系统以IRouter对象为核心,我们姑且将其称为IRouter路由.本章介绍的是最早发布于ASP.NET Core 2.2中 ...
- Laya 踩坑日记-人物模型穿模,模型显示不正常
最近做游戏,人物要跑到很远的位置,z轴距离大概有20000个单位,然后就发现一个bug,到远处人物模型穿了,而且没办法改,这就尴尬了 Z轴对应值 0 100000 100000 当距离零点 ...
- 【SpringMVC】SpringMVC 异常处理
SpringMVC 异常处理 文章源码 异常处理思路 系统中异常包括两类:预期异常和运行时异常,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生. Cont ...
- 【剑指Offer】链表的基本操作之创建、插入、删除
// C++ #include<iostream> using namespace std; //链表的定义 struct ListNode { int val; ListNode* ne ...
- PAT天梯赛练习 L3-004 肿瘤诊断 (30分) 三维BFS
题目分析: 可能是我的理解能力比较差,在读题的时候一直以为所有的切片是可以排列组合的,并不是按照输入顺序就定死的,那么这题就变得十分的复杂啦~~~~~,查看的题解之后发现所有的切片并没有所谓的自由组合 ...