数位DP详解
算法使用范围
在一个区间里面求有多少个满足题目所给的约束条件的数,约束条件必须与数自身的属性有关
下面用kuangbin数位dp的题来介绍
例题 不要62
题意:在一个区间里面求出有多少个不含4和62的数
做法:平常我们的做法肯定是从L枚举到R,然后数位上一个一个的判断,但是如果是范围过大的话我们不可以预处理存储,只能每次去枚举,这样肯定会超时
所以我们使用数位DP 记忆化搜索,我们dfs去枚举每一位
数位部分
例如 2567
因为千位是2的时候我的百位只能从0-5,1的时候百位却是可以0-9,所以这点我们需要判断
第一位 0-2
第二位 第一位==2?5:9
第三位 第一位==5&&第二位==6?6:9
第四位 第一位==5&&第二位==6&&第三位==6&&第四位==7?7:9
上面也就是数位的过程,数位我们就省去了每一个数去拆分查询的复杂度,大概是复杂度/10
dfs参数
虽然参数比较灵活,但是有几个固定的参数,做多了数位DP的题也会觉得这个参数其实是一个套路了
第一个参数
从上述的的枚举,我们可以看出我们首先要把这个数拆分到数组里面,然后dfs每位每位的枚举
ans 代表的是当前枚举到(个,十,百,千...)位了
第二个参数
从上述来看,每到一位我都需要知道前面是不是上边界的值,从而来确定我的当前位的枚举范围
flag 代表的数前一位是否还是处于上边界
第三个参数
iszero 用来判断前导0,有些题目会有前导零的要求,比如说求二进制的数位的时候他前面
第四个参数
issix 比如说这题的话,那么我就要判断前面一位是否是6,那么当前位是2的话那么我就可以忽略了,这个参数要灵活变通
dp部分
dp的主要目的就是尽量把大部分重复问题的东西通过巧妙的存储保留下来,下次可以直接访问,大大减少复杂度
dp[ans][issix]
数组的用途解析(注意!!!)
ans代表剩余的位数,issix代表是否是6
注意的是我们必须保存的是和数的属性相关的东西,比如求数不包括62的,我们求1-1000和1-10000以内和数自己都是没关系的,他每次依然会被计数
现在我知道 1-1000的62的个数 说明这是前面是没有6的情况
我要求42000-50000之间有多少个
我们就会发现其实42000-43000其实和1-1000其实是一样的,43000-50000也是一样的道理,所以我们可以用,这是前缀没有6的情况 dp[ans][0]
我要求60000-63000之间有多少个
因为前面有6了,所以我们这个我们最开始用不到1-1000内62的个数这个条件,我们跑出60000-61000的结果时
我们就相当于知道了dp[ans][1]的值,后面61000-63000我们都可以使用
因为有前面是6和不是6的情况,所以我们列要开两种情况
列标这里的作用比较灵活
比如我们要求这个数的数位和能否被10整除
那么我们就要开10,分别对应前缀%10对后面的影响
总之,前缀提供啥,后面要根据前缀的情况来给出不同情况的值,前缀必须没有后效性,前缀不用知道后缀就可以满足情况
小优化
有些题它可能是多组输入,我们就可以把memset(dp,-1,sizeof(dp))放多组输入外面
回到开始说的必须是数的属性,永远不会变,所以我们计算后的数组可以保留到下一组数据使用
当然如果是把上列说的%10改成输入%q那就不行了
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[][];
int a[];
int sum=;
int dfs(int len,bool issix,bool flag)//len代表当前枚举的位,issix代表前面是否是6,flag代表是否还是处于上边界
{
if(len==-) return ;//依题意来决定是1还是0,这题只是计算没有62的,所以我们直接返回1,如果题目是数位和是否%10==0就不一样了
int up=flag?a[len]:;//决定你枚举的上界是多少
if(!flag&&dp[len][issix]!=-) return dp[len][issix];//如果不是处于上边界并且之前求过了值的话可以直接返回
int tmp=;
for(int i=;i<=up;i++)
{
if(i==) continue;
if(issix&&i==) continue;
tmp+=dfs(len-,i==,flag&&i==a[len]);//上次是上边界,如果这次也是的话,那么下一位也是
}
if(!flag) dp[len][issix]=tmp;//求了值用dp存下,下次可以使用
return tmp;
}
int suan(int x)
{
int pos=;
while(x)
{
a[pos++]=x%;
x/=;
}
return dfs(pos-,,true);
}
int main()
{
int l,r;
memset(dp,-,sizeof(dp));
while(scanf("%d%d",&l,&r)!=EOF)
{
if(l==&&r==) break;
printf("%d\n",suan(r)-suan(l-));//利用前缀和来求固定区间的个数
}
}
这里讲一下为什么都是!flag的时候来进行操作
首先看这句
if(!flag&&dp[len][issix]!=-1) return dp[len][issix];
因为如果我现在知道了1-1000的个数
我现在要求2100-2500
那么求到2???的时候需不需要返回呢,答案是不行,
因为你返回的值里面还包括了 2500-3000的值,所以需要加!flag
再看这句
if(!flag) dp[len][issix]=tmp;
同样如果我求了到了2000-2500的值的时候我需不要把他当1-1000的值进行存储呢
也不行,因为你烧了2500-3000这一段没算
最后再推荐一篇大牛的博客
https://blog.csdn.net/jk211766/article/details/81474632
数位DP详解的更多相关文章
- 数位DP 详解
序 天堂在左,战士向右 引言 数位DP在竞赛中的出现几率极低,但是如果不会数位DP,一旦考到就只能暴力骗分. 以下是数位DP详解,涉及到的例题有: [HDU2089]不要62 [HDU3652]B-n ...
- 动态规划晋级——HDU 3555 Bomb【数位DP详解】
转载请注明出处:http://blog.csdn.net/a1dark 分析:初学数位DP完全搞不懂.很多时候都是自己花大量时间去找规律.记得上次网络赛有道数位DP.硬是找规律给A了.那时候完全不知数 ...
- 数位dp详解&&LG P2602 [ZJOI2010]数字计数
数位dp,适用于解决一类求x~y之间有多少个符合要求的数或者其他. 例题 题目描述 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除 ...
- 状压DP详解(位运算)
前言: 状压DP是一种非常暴力的做法(有一些可以排除某些状态的除外),例如dp[S][v]中,S可以代表已经访问过的顶点的集合,v可以代表当前所在的顶点为v.S代表的就是一种状态(二进制表示),比如 ...
- 状态压缩dp 状压dp 详解
说到状压dp,一般和二进制少不了关系(还常和博弈论结合起来考,这个坑我挖了还没填qwq),二进制是个好东西啊,所以二进制的各种运算是前置知识,不了解的话走下面链接进百度百科 https://baike ...
- 线性DP详解
顾名思义,线性DP就是在一条线上进行DP,这里举一些典型的例子. LIS问题(最长上升子序列问题) 题目 给定一个长度为N的序列A,求最长的数值单调递增的子序列的长度. 上升子序列B可表示为B={Ak ...
- 状态压缩动态规划(状压DP)详解
0 引子 不要999,也不要888,只要288,只要288,状压DP带回家.你买不了上当,买不了欺骗.它可以当搜索,也可以卡常数,还可以装B,方式多样,随心搭配,自由多变,一定符合你的口味! 在计算机 ...
- 状压DP详解+题目
介绍 状压dp其实就是将状态压缩成2进制来保存 其特征就是看起来有点像搜索,每个格子的状态只有1或0 ,是另一类非常典型的动态规划 举个例子:有一个大小为n*n的农田,我们可以在任意处种田,现在来描述 ...
- 树形DP详解+题目
关于树形dp 我觉得他和线性dp差不多 总结 最近写了好多树形dp+树形结构的题目,这些题目变化多样能与多种算法结合,但还是有好多规律可以找的. 先说总的规律吧! 一般来说树形dp在设状态转移方程时都 ...
随机推荐
- 【IOS学习】【Swift语言】
基本语法: OS X playground 引入 import Cocoa IOS playground 引入 import UIKit 基本数据类型 let 定义常量 定义完成之后无法修改 var ...
- Confluence 6 文档主题合并问答
在 Confluence 官方 前期发布的消息 中,文档主题在 Confluence 6.0 及其后续版本中已经不可用.我们知道你可能对这个有很多好好奇的问题,因此我们在这里设置了一个问答用于帮助你将 ...
- mysqli的使用
<?php /** 数据库连接 **/ $conn=mysqli_connect('localhost:3306','root','root'); if(!$conn){ die("c ...
- appium自动化测试(四)
一. 获取webview的html页面 方法一: 1. 获取webview中对应的html页面 谷歌浏览器中输入地址:chrome://inspect(第一次使用要FQ) 前提:手机开启USB调试模式 ...
- Hash应用例子
八.Hash应用例子搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复 ...
- windows 系统使用 git 和码云管理代码(本地已有项目)
1. 为本地项目创建本地仓 找到项目所在的根目录(目录下有解决方案那个),右击目录,点击右键菜单中的“Git Bash Here”(前提是你的电脑已经装了Git,我用的是TortoiseGit) 然后 ...
- Git:合并分支冲突问题
首先创建分支feature1,并且修改readme.txt内容,再在该分支上提交. 切换回master分支, 并且修改readme.txt内容,再在master分支上提交. 此时的分支路线如下,合并时 ...
- [luogu P3275] [SCOI2011]糖果
[luogu P3275] [SCOI2011]糖果 题目描述 幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一些 ...
- Vuex的深入学习
1.vuex 的dispatch和commit提交mutation的区别 (1)当你的操作行为中含有异步操作,比如向后台发送请求获取数据,就需要使用action的dispatch去完成了.其他使用co ...
- Redis分布式锁实例
maven依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</ ...