数位dp入门(内容一样,新版格式)
顾名思义,数位dp,是一种用来计数的dp,就是把一个数字拆成一个一个数位去统计
如果现在给你一道题,需要你求在区间[l,r]内满足条件的解的个数,我们很容易想到去暴力枚举,但要是数据范围太大这种办法就行不通了,这时候数位dp就派上了用场,所谓数位就是把一个数拆成一个一个进制位,然后逐一比较看是否满足题目要求,这其实也是一种暴力方法,只不过时间复杂度小了很多
那么到底要如何做呢?下面我们来看一道例题
HDU2089
概括一下题目意思
就是给你一个区间[n,m],要你求区间内不含"62"或"4"的数字的个数,如8134(含4),21262455(含62)均不满足题意,而61342这种"6"和"2"并不连在一起的数字则满足题意
直接统计对于暴力枚举很好求,但是对于数位dp并不容易,所以我们还需要用到差分的思想,即统计0到b+1(注意不是b,至于为什么后面会讲)和0到a的满足条件的个数,再两者相减
进一步化简,求 \(0\) 到 \(i\) 位数不含 \(4\) 和 \(62\) 的个数
\(i=1\),求 \(0\)~\(9\) 的满足条件的个数
\(i=2\),求 \(0\)~\(99\) 的满足条件的个数
\(i=3\),求 \(0\)~\(999\) 的满足条件的个数
\(i=4\),求 \(0\)~\(9999\) 的满足条件的个数
...
用 \(dp[i][0]\) 表示 \(i\) 位数中幸运数的个数
用 \(dp[i][1]\) 表示 \(i\) 位数中以 \(2\) 开头的幸运数的个数
用 \(dp[i][2]\) 表示 \(i\) 位数中非幸运数的个数
那么,就有以下的递推公式
\(dp[i][0]=dp[i-1][0]\times9-dp[i-1][1]\)
表示前i-1位数字中的幸运数前面加上除4以外的0~9的其他数字,共9个,还要减去前i-1位数字中的以2开头的幸运数加上这一位以6开头的数字的个数
\(dp[i][1]=dp[i-1][0]\)
表示前i-1位数字中的幸运数加上这一位的2
\(dp[i][2]=dp[i-1][2]\times10+dp[i-1][1]+dp[i-1][0]\)
表示前面已经不合法的数字这一位无论放什么都不合法,所以0~9随便放,加上前i-1位数字中的以2开头的幸运数加上这一位的6,再加上前i-1位数字中的幸运数加上这一位的4的个数
初始值 \(dp[0][0]=1\),其他均为 \(0\)
根据初始值和递推公式,我们就能得到从 \(0\) 到任意 \(i\) 位数字的吉利数字的个数。
找到 \(0\) ~ \(n\) 的吉利数字的个数
我们先求出 \(0\) ~ \(n\) 之间非吉利数字的个数,用总数减去即可。那,非吉利数字的个数怎么求呢?
用具体的数字举例来说吧:设 \(n = 583626\)
用 \(digit[10]\) 记录 \(n+1\)每一位对应的数字,此例中有 \(6\) 位数字(令 \(cnt = 6\) 表示数字位数),分别是
digit[6] = 5
digit[5] = 8
digit[4] = 3
digit[3] = 6
digit[2] = 2
digit[1] = 7
digit[0] = 任意数字,占位用的
用 \(sum\) 记录非吉利数字的个数,初始化为 \(0\)
需要一个\(bool\) 量 \(flag\),记录是否出现了非吉利数字。初始化为 \(false\), 未出现。
我们从数字的最高位起进行判断: \(digit[6] = 5\).
我们要求 \(0\) ~ \(499999\) 之间非吉利数的个数。
首先:加上\(0\) ~ \(99999\) 中所有非吉利数字前面添加0~4的任意一个数字的情况 \(sum += dp[5][2] \times digit[6]\)
其次:\(5\) 大于 \(4\),故我们要加上 \(0\) ~ \(99999\) 中所有吉利数字前面添加 \(4\) 的情况 \(sum += dp[5][0]\)
接着,判断第\(5\)位 \(digit[5] = 8\),即判断 \(500000\) ~ \(579999\) 之间的非吉利数字的个数,其实就是判断 \(0\) ~ \(79999\) 之间的,前面的数字不是\(6\)就没有什么用
首先:加上\(0\) ~ \(9999\) 中所有非吉利数字前面添加 \(0\)~\(7\) 的任意一个数字的情况 \(sum += dp[4][2] \times digit[5]\)
其次:\(8\) 大于 \(4\),故我们要加上 \(0\)~\(9999\) 中所有吉利数字前面添加4的情况 \(sum += dp[4][0]\)
此外:\(8\) 大于 \(6\),故我们要加上 \(0\)~\(9999\) 中所有以\(2\)开头的吉利数字前添加\(6\)的情况 \(sum += dp[4][1]\)
接着,判断第 \(4\) 位 \(digit[4] = 3\),即判断 \(580000 ~ 582999\) 之间的非吉利数字的个数,其实就是判断 \(0\) ~ \(2999\) 之间的
首先:加上 \(0\) ~ \(999\)中所有非吉利数字前面添加 \(0\)~\(2\)的任意一个数字的情况 \(sum += dp[3][2] \times digit[4]\)
其次:\(2\) 小于 \(4\),没有需要特别考虑的
此外:\(2\) 小于 \(6\),没有需要特别考虑的
接着,判断第 \(3\) 位 \(digit[3] = 6\),即判断 \(583000\) ~ \(583599\) 之间的非吉利数字的个数,其实就是判断\(0\) ~ \(599\)之间的
首先:加上 \(0\) ~ \(99\) 中所有非吉利数字前面添加 \(0\)~\(5\) 的任意一个数字的情况 \(sum += dp[2][2] \times digit[3]\)
其次:\(6\) 大于 \(4\),故我们要加上 \(0\) ~ \(99\) 中所有吉利数字前面添加4的情况 \(sum += dp[2][0]\)
接着,判断第 \(2\) 位 \(digit[2] = 2\),即判断 \(583600 ~ 583619\) 之间的非吉利数字的个数,其实就是判断0 ~ 19之间的,
首先:加上 \(0\) ~ \(9\) 中所有非吉利数字前面添加 \(0\)~\(1\) 的任意一个数字的情况 \(sum += dp[1][2] \times digit[2]\)
其次:\(2\)小于\(4\),没有需要特别考虑的
此外:\(2\)小于\(6\),没有需要特别考虑的
但是,需要注意的是,这里判断的数字出现了 \(62\),我们要把\(flag\)标识为\(true.\)
最后,判断第 \(1\) 位 \(digit[1] = 7\), 判断 \(583620 ~ 583626\) 但是这里 \(flag\) 为 \(true\) 了,表示前面的数字里面已经包含了非吉利数字,所以后面需要把所有的数字情况都加入到非吉利里面。(正是因为每次判断的数字末尾都比该位的数字少1,所以最开始要记录 \(n + 1\) 的值)
\(sum += digit[1] \times dp[0][2] + digit[1] \times dp[0][0]\)
Code
#include<bits/stdc++.h>
#define in(i) (i=read())
using namespace std;
int read() {
int ans=0,f=1; char i=getchar();
while(i<'0'||i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0'&&i<='9') {ans=(ans<<3)+(ans<<1)+i-'0'; i=getchar();}
return ans*f;
}
int dp[10][3],digit[15];
void init() {
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=8;i++) {
dp[i][0]=dp[i-1][0]*9-dp[i-1][1];
dp[i][1]=dp[i-1][0];
dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0];
}
}
int solve(int x)
{
memset(digit,0,sizeof(digit));
int cnt=0,tmp=x;
while(tmp) {
digit[++cnt]=tmp%10;
tmp/=10;
}
digit[cnt+1]=0; int flag=0,ans=0;
for(int i=cnt;i>=1;i--) {
ans+=digit[i]*dp[i-1][2];
if(flag) ans+=digit[i]*dp[i-1][0];
else {
if(digit[i]>4) ans+=dp[i-1][0];
if(digit[i]>6) ans+=dp[i-1][1];
if(digit[i+1]==6 && digit[i]>2) ans+=dp[i][1];
}
if(digit[i]==4 || (digit[i+1]==6 && digit[i]==2)) flag=1;
}
return x-ans;
}
int main()
{
int a,b; init();
while(1) {
in(a);in(b);
if(!a && !b) break;
cout<<solve(b+1)-solve(a)<<endl;
}
return 0;
}
最后说那个 \(b+1\) 的情况,我们看到代码中有判断 \(digit[i]>4\) 和 \(digit[i]>6\) 等类似的语句,我们处理第 \(i\)位时,实际上是处理 \(0~digit[i]-1\),即 \([ 0,digit[i] )\) ,而把 \(digit[i]\) 放到下一次去判断,但我们处理个位时,最后一个是不会去统计的,所以我们把统计的范围\(+1\),即为\([ 0,digit[i]+1 )\Rightarrow[ 0,digit[i] ]\),所以就有了 \(solve(b+1)-solve(a)\) 这样的语句.(还是高二 \(dalao\) Navi-Awson告诉我的,\(\%\%\%\))
数位\(dp\)记忆化搜索写法
Code
#include<bits/stdc++.h>
#define in(i) (i=read())
using namespace std;
typedef long long lol;
lol read() {
lol ans=0,f=1;
char i=getchar();
while(i<'0'|| i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
return ans*f;
}
lol a,b;
lol dp[19][11];
lol bit[19];
lol dfs(lol len,lol pre,lol limit) {
if(!len) return 1;//如果搜到最后一位了,返回下界值1
if(!limit && dp[len][pre]) return dp[len][pre];//记忆化部分
lol maxn=limit?bit[len]:9;//求出最高可以枚举到哪个数字
lol ans=0;
for(lol i=0;i<=maxn;i++) {
if(i!=4 && !(pre && i==2))//如果这一位不为4并且上一位不为6且这一位不为2
ans+=dfs(len-1,i==6,limit && i==maxn);//满足条件
}
if(!limit) dp[len][pre]=ans;//如果没有限制,代表搜满了,可以记忆化,否则就不能
return ans;
}
lol solve(lol a) {
memset(bit,0,sizeof(bit));
lol k=0;
while(a) {//取出数字的每一位
bit[++k]=a%10;
a/=10;
}
return dfs(k,0,1);
}
int main()
{
//freopen("number.in","r",stdin);
//freopen("number.out","w",stdout);
lol t; in(t);
for(int i=1;i<=t;i++) {
lol a,b; in(a);in(b);
cout<<solve(b)-solve(a-1)<<endl;//差分思想
}
return 0;
}
应用
放几道题目上来
数位dp入门(内容一样,新版格式)的更多相关文章
- xbz分组题B 吉利数字 数位dp入门
B吉利数字时限:1s [题目描述]算卦大湿biboyouyun最近得出一个神奇的结论,如果一个数字,它的各个数位相加能够被10整除,则称它为吉利数.现在叫你计算某个区间内有多少个吉利数字. [输入]第 ...
- 数位dp入门 hdu2089 不要62
数位dp入门 hdu2089 不要62 题意: 给定一个区间[n,m] (0< n ≤ m<1000000),找出不含4和'62'的数的个数 (ps:开始以为直接暴力可以..貌似可以,但是 ...
- hdu3555 Bomb 数位DP入门
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555 简单的数位DP入门题目 思路和hdu2089基本一样 直接贴代码了,代码里有详细的注释 代码: ...
- HDU 2089 不要62【数位DP入门题】
不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- HDU 2089 不要62(数位dp入门)
题意:统计区间 [a,b] 中不含 4 和 62 的数字有多少个. 题解:这是数位DP的入门题了,首先要理解数DP的原理,DP[i][j]:代表第i位的第j值,举个栗子:如4715 数位数是从右向 ...
- HDU 2089 - 不要62 - [数位DP][入门题]
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 Time Limit: 1000/1000 MS (Java/Others) Memory Li ...
- LightOJ 1140 计数/数位DP 入门
题意: 给出a,b求区间a,b内写下过多少个零 题解:计数问题一般都会牵扯到数位DP,DP我写的少,这道当作入门了,DFS写法有固定的模板可套用 dp[p][count] 代表在p位 且前面出现过co ...
- HDU-2089不要62-暴力或数位DP入门
不要62 题意:给定区间,求在这个区间中有多少个数字,不包含4且不包含62: 这道题作为数位DP的入门题: 暴力也是可以过 #include<cstdio> #include <io ...
- 数位dp入门 HDU 2089 HDU 3555
最基本的一类数位dp题,题目大意一般是在a~b的范围,满足某些要求的数字有多少个,而这些要求一般都是要包含或者不包含某些数字,或者一些带着数字性质的要求,一般来说暴力是可以解决这一类问题,可是当范围非 ...
随机推荐
- Online Meeting CodeForces - 420B (思维)
大意: 给定某一段连续的上线下线记录, 老板上线或下线时房间无人, 并且每次会议都在场, 求哪些人可能是老板. 结论1: 从未出现过的人一定可以是老板. 结论2: 出现过的人中老板最多只有1个. 结论 ...
- Java面试知识点汇总
Java面试知识点汇总 置顶 2019年05月07日 15:36:18 温柔的谢世杰 阅读数 21623 文章标签: 面经java 更多 分类专栏: java 面试 Java面试知识汇总 版权声明 ...
- MySQL 事务、视图、索引
一.事务(Transaction) 1.1 什么是事务? SQL中,事务是指将一系列数据操作捆绑成为一个整体进行统一管理. 如果一个事务执行成功,该事务中进行的所有数据均会提交,称为数据库中的永久组成 ...
- ASP.NET使用AJAX应注意IIS有没有.ashx扩展
项目添加引用AJAX.DLL了:今天将本地做好的一个web程序放到服务器上,居然报告错误了.web程序使用了ajax来往返数据. 检查生成的html语句,有这么两句代码<script type= ...
- 11 Django实现WebSocket
因为需要实时显示状态的需求,想到了websocket,但是Django原生不支持websocket,后来搜索到了chango-channels项目,可以实现次需求. 一.Channels 官方文档 二 ...
- vue项目中实现图片懒加载的方法
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载.这样子对于页面加载性能上会有很大的提升,也提高了用户体验. 实 ...
- 6 java 笔记
1 java的类通过构造器来创建该类的对象 2 java提供extends关键字来实现子类继承父类 3 初始化块总是在构造器调用之前被执行 4 可以吧java中的类当成一种自定义的类型 5 类定义的变 ...
- wabacus JaveEE开发框架
http://www.wabacus.org/ css学习网站:http://www.divcss5.com/rumen/r422.shtml
- 【Swift后台】背景介绍
在2017年11月的时候,就已经对Swift后台进行过研究,简书上发表过相应文章,那时候发表的是单纯的对Vapor文档的翻译,此次则是作为进一步研究的学习笔记来保存. Swift后台的本质,主要是Va ...
- redis整合Spring入门
首先 衷心感谢这篇博客给我入门时的启发 三颗心脏 你需要知道,spring的官方文档中已经注明,与redis整合时,spring的jar包版本不能低于4.2.6,否则不支持,会报错的哟 测试的时候请 ...