学习博客:

戳这里

戳这里

“在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个D 进制数或
此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
用数位的性质,设计log(n)级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。”——刘聪

事实上,为什么会想到用数位DP来做,就是因为限定条件往往和数位有关,而仔细地朴素的暴力方法中,所做的重复的工作太多。这样的条件会使得DP(记忆化搜索)有用武之地。

比如如果我们要统计[0,54321]中满足某个条件的个数,需要将其拆分为

[00000,09999][10000,19999],[20000,29999],[30000,39999],[40000,49999],

[50000,50999],[51000,51999],[52000,52999],[53000,53999],

[54000,54099],[54100,54199],[54200,54299],

[54300,54309],[54310,54319],

[54320,54321]

为什么要这么分呢?随便举个例子,如果我们统计过了[0000,9999]中的满足条件(或者其他各种不满足条件的状态)的个数,那么分别在加上前缀,就可以判断出有多少个满足条件的个数。目的是为了将大的区间划分为小的区间进行求解。

因此,总结一句话,数位DP减少的运算量为:前面几位固定,后面几位可以任意取的个数统计。

模板:

 1 typedef long long ll;
 2 int a[20];
 3 ll dp[20][state];//不同题目状态不同
 4 ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
 5 {
 6     //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
 7     if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
 8     //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
 9     if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
10     /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
11     int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
12     ll ans=0;
13     //开始计数
14     for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
15     {
16         if() ...
17         else if()...
18         ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
19         /*这里还算比较灵活,不过做几个题就觉得这里也是套路了
20         大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
21         去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
22         要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,
23         前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
24     }
25     //计算完,记录状态
26     if(!limit && !lead) dp[pos][state]=ans;
27     /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
28     return ans;
29 }
30 ll solve(ll x)
31 {
32     int pos=0;
33     while(x)//把数位都分解出来
34     {
35         a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行
36         x/=10;
37     }
38     return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
39 }
40 int main()
41 {
42     ll le,ri;
43     while(~scanf("%lld%lld",&le,&ri))
44     {
45         //初始化dp数组为-1,这里还有更加优美的优化,后面讲
46         printf("%lld\n",solve(ri)-solve(le-1));
47     }
48 }

View Code

练手题目1:戳这里

题意:求1-n内有多少个数满足各位之和整除该数。

解题思路:数位dp,枚举各位之和。

附ac代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<stdio.h>
 4 #include<string.h>
 5 #include<string>
 6 using namespace std;
 7 typedef long long ll;
 8 int a[22];
 9 ll dp[20][220][220];//不同题目状态不同
10 int mod;
11 ll dfs(int pos, int state/*state变量*/, int r/*其他记录点,在这里是余数*/, bool limit/*数位上界变量*/)
12 {
13     //递归边界,既然是按位枚举,最低位是0,那么pos==0说明这个数我枚举完了
14     if(pos == 0) return (state == mod && !r);
15     /*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,
16     也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。
17     不过具体题目不同或者写法不同的话不一定要返回1 */
18     //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
19     if(dp[pos][state][r] != -1 && !limit) return dp[pos][state][r];
20     /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应*/
21     int up = limit?a[pos]:9;//根据limit判断枚举的上界up;
22     ll ans = 0;
23     //开始计数
24     for(int i = 0; i <= up; ++i)
25     {
26         if(i + state > mod) break;//剪枝
27         ans += dfs(pos - 1, state + i, (r * 10 + i) % mod, limit && i == a[pos]);//最后两个变量传参都是这样写的
28         /*这里还算比较灵活,不过做几个题就觉得这里也是套路了
29         大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
30         去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
31         要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,
32         前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
33     }
34     //计算完,记录状态
35     if(!limit) dp[pos][state][r] = ans;
36     /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
37     return ans;
38 }
39 ll solve(ll n)
40 {
41     int pos = 0;
42     ll x = n;
43     while(x)//把数位都分解出来
44     {
45         a[++pos] = x % 10;//个人老是喜欢编号为[1,pos],看不惯的就按自己习惯来,反正注意数位边界就行
46         x /= 10;
47     }
48     ll ans = 0;
49     for(int i = 1; i <= 9 * pos; ++i)//枚举模
50     {
51         mod = i;
52         //初始化dp数组为-1
53         memset(dp, -1, sizeof(dp));
54         ans += dfs(pos/*从最高位开始枚举*/, 0, 0/*一系列状态 */, true);//刚开始最高位都是有限制的,显然比最高位还要高的一位视为0嘛
55     }
56     return ans;
57 }
58 int main()
59 {
60     int t;
61     scanf("%d", &t);
62     ll n;
63     for(int cas = 1; cas <= t; ++cas)
64     {
65         scanf("%lld", &n);
66         printf("Case %d: %lld\n", cas, solve(n));
67     }
68
69
70 }

View Code

练手题目2:戳这里

题意:求l-r中有多少个数满足各位>0的数不大于三个。

解题思路:模板题略作修改。

附ac代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 22;
 4 typedef long long ll;
 5 int a[maxn];
 6 ll dp[maxn][maxn];
 7 ll dfs(int pos, int stat, bool lim)
 8 {
 9     if(!pos) return 1;
10     if(!lim && stat <= 3 && dp[pos][stat] != -1) return dp[pos][stat];
11     int up = lim?a[pos]:9;
12     ll ans = 0;
13
14     for(int i = 0; i <= up; ++i)
15     {
16         if(stat + (i > 0) <= 3)
17         {
18             ans += dfs(pos - 1, stat + (i > 0), lim && i == a[pos]);
19         }
20     }
21     if(!lim && stat <= 3) dp[pos][stat] = ans;
22     return ans;
23 }
24 ll solv(ll x)
25 {
26     int pos = 0;
27     while(x)
28     {
29         a[++pos] = x % 10;
30         x /= 10;
31     }
32
33     return dfs(pos, 0, true);
34 }
35 int main()
36 {
37     int t;
38     scanf("%d", &t);
39     memset(dp, -1, sizeof(dp));
40     ll l, r;
41     while(t--)
42     {
43         scanf("%lld %lld", &l, &r);
44         printf("%lld\n", solv(r) - solv(l - 1));
45     }
46     return 0;
47 }

View Code

转载于:https://www.cnblogs.com/zmin/p/9438429.html

数位dp【模板 + 老年康复】相关推荐

  1. 数位dp模板 [dp][数位dp]

    现在才想到要学数位dp,我是不是很弱 答案是肯定的 以一道自己瞎掰的题为模板 1 //题: 2 //输入数字n 3 //从0枚举到n,计算这n+1个数中含有两位数a的数的个数 4 //如12930含有 ...

  2. 不要62(HDU-2089)(数位DP模板题)

    杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer). 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍 ...

  3. 数位DP入门+数位DP模板

    数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数.所谓数位dp,字面意思就是在数位上进行dp咯.数位还算是比较好听的名字,数位的含义:一个数有个位.十位.百位.千 ...

  4. HDU 3555 Bomb(数位DP模板啊两种形式)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555 Problem Description The counter-terrorists found ...

  5. 数位dp模板 最高位最低位_无纸化办公入门指南(数位板篇)

    居家办公的这段时间,你们有没有遇到无纸化办公的问题? 作为一名编辑,在我社规定不能寄纸稿的情况下,看电子稿成了常态.如果是word版尚且可以批注,不影响工作效率.但如果是PDF呢?怎样保证改稿的效率? ...

  6. 不要62 ---数位DP

    题意:求m到n中不含62和4的数的个数. 题目链接 思路:数位dp模板求满足的数字或不满足的数字,刚学,就求不满足的数. #include<stdio.h> #include<str ...

  7. HDU 2089 不要62 数位DP

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2089 题目描述: 给定一个区间, 让你求这个区间中所有不带62 或 4 的数的个数 解题思路: 很明 ...

  8. HDU 6156 2016ICPC网络赛 G: Palindrome Function(数位DP)

    题意: 已知函数f(x, k),如果10进制数x在k进制下是个回文数,那么f(x, k)值为k,否则为1 现给出l, r, x, y, 求出∑∑f(i, j)  (l<=i<=r)  (x ...

  9. P2657 [SCOI2009] windy 数(数位DP)

    题目链接:[SCOI2009] windy 数 - 洛谷 这是一道需要考虑前导0的数位DP题,为什么需要考虑前导0呢?其实原因很简单,因为有条件限制我们相邻两个数的差,所以我们在进行数位DP时必须把前 ...

最新文章

  1. 【好资源】473页斯坦福数学基础:《应用线性代数》(附pdf和ppt下载)
  2. ICLR 2019计算机视觉、NLP、图模型、对抗学习、表示学习和元学习
  3. BFC与垂直外边距折叠笔记
  4. GPU(CUDA)学习日记(九)------ CUDA存储器模型
  5. pytorch 图像分割的交并比_级联多对抗的LAPGAN(二)pytorch实现
  6. 玩转u-boot之【初探环境变量env/bootcmd/bootargs】
  7. leetcode 354. 俄罗斯套娃信封问题(dp+二分)
  8. windows内置的linux安卓驱动多系统摆脱虚拟机(下)
  9. CentOS+Subversion
  10. 数据结构 创建顺序表
  11. Deep Learning for 3D Recognition
  12. Cocos2d-x物理引擎概述
  13. 【转】进程死锁及解决方法
  14. 小米游戏本bios_小米游戏本2019款,原来他才是电脑的终极境界
  15. poj 3295 Tautology
  16. openldap范例数据库mysql_用mysql作openldap的后台数据库
  17. 三国霸王大陆服务器维护,霸王大陆修改教程
  18. Springboot中EasyExcel导出及校验后导入前后台功能实现
  19. Linux 命令(189)—— init 命令
  20. 手把手教你配置哔哔点啥

热门文章

  1. python软件编程培训
  2. linux:nohup命令用法
  3. 什么是接口测试?测试人员为什么要做接口测试?
  4. 北京工商大学计算机考研818真题,北京工商大学考研真题汇总
  5. Science上聚类算法论文——Clustering by fast search and find of density peaks翻译稿
  6. mbedtls 库基础及其应用
  7. 搞定机器学习面试,这些是基础!(下载PDF)
  8. 解析单总线协议(1-wire)
  9. java中getchars是什么意思_java中的getChars()方法
  10. mysql锁表查询和解锁操作