学习博客:

戳这里

戳这里

“在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个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 }

练手题目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 }

练手题目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 }

数位dp【模板 + 老年康复】的更多相关文章

  1. HDU 2089 不要62(数位dp模板题)

    http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:求区间内不包含4和连续62的数的个数. 思路: 简单的数位dp模板题.给大家推荐一个好的讲解博客.h ...

  2. POJ 3286 How many 0's(数位DP模板)

    题目链接:http://poj.org/problem?id=3286 题目大意: 输入n,m,求[n,m]的所有数字中,0出现的总数是多少,前导零不算. 解题思路: 模板题,设dp[pos][num ...

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

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

  4. 51nod 1009 数字1的数量(数位dp模板)

    给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数. 例如:n = 12,包含了5个1.1,10,12共包含3个1,11包含2个1,总共5个1.   数位dp的模板题   ...

  5. 51nod 1009 - 数字1的数量 - [数位DP][模板的应用以及解释]

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1009 基准时间限制:1 秒 空间限制:131072 KB 给 ...

  6. HDU - 4722 Good Numbers 【找规律 or 数位dp模板】

    If we sum up every digit of a number and the result can be exactly divided by 10, we say this number ...

  7. 数位dp 模板加例题

    概念:所谓数位"dp",是指对数字的"位"进行的与计数有关的DP.一个数一个位,十位,百位,千位等,数的每一位就是数位.数位DP用来解决与数字操作有关的问题.例 ...

  8. 【hdu6148】Valley Numer【数位dp模板题】

    题意 对于每组数据给出一个整数n(length(n)<=100),找出不大于n的数字中有多少是Valley Numer.对于Valley的定义是它每一位的数字要么是递增,要么是递减,要么是先递减 ...

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

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

随机推荐

  1. DC-DC变换器,24v转5v稳压芯片,3A输出电流

    在24V输入中,比较合适的LDO可以选择:PW6206,输出电压3V,3.3V,5V 输入电压最高40V,功耗也低4uA左右,采用SOT23-3封装. PW6206系列是一个高精度,高输入电压低静态电 ...

  2. 2.4V升3.3V,2.4V升3V,1A大电流升压芯片

    两节镍氢电池串联就是1.2V+1.2V=2.4V的供电电压了,2.4V升3V, 2.4V升3.3V的话,就能稳压稳定给模块供电了,镍氢电池是会随着使用的电池电量减少的话,电池的电压也是跟着变化的,导致 ...

  3. Bitter.Core系列四:Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore ORM 之 示例 查询

    一: 单表模型驱动查询 如下示例代码演示: // 根据ID 查询: var studentquery = db.FindQuery<TStudentInfo>().QueryById(12 ...

  4. Dubbo 最基本的几个需求

    需求 http://dubbo.apache.org/zh-cn/docs/user/preface/requirements.html 在大规模服务化之前,应用可能只是通过 RMI 或 Hessia ...

  5. LeetCode上并发题目无Go版本:台湾同胞试水 — 交替打印FooBar

    https://mp.weixin.qq.com/s/I5va3PI1oGIj8R_n3Nw2yw

  6. Linux进程内存用量分析之堆内存篇

    https://mp.weixin.qq.com/s/a6mLMDinYQGUSaOsGYCEaA 独家|Linux进程内存用量分析之堆内存篇 姬晨烜 58技术 2019-12-06 导语 本文将介绍 ...

  7. 【Coredump】调试之旅

    测试反馈,core了. 拿到环境,发现6和11,一个是重复释放,一个是非法指针. 用GDB一挂 ,发现 1 GNU gdb (GDB) 7.5 2 Copyright (C) 2012 Free So ...

  8. LOJ10069 TREE

    题目描述 原题来自:2012 年国家集训队互测 给你一个无向带权连通图,每条边是黑色或白色.让你求一棵最小权的恰好有 need 条白色边的生成树.题目保证有解. 输入格式 第一行 V,E,need 分 ...

  9. NOI Linux 快速入门指南

    目录 关于安装 NOI Linux 系统配置 网络 输入法 编辑器 1. gedit 打开 配置 外观展示 2. vim 打开 配置 使用 makefile 编译运行 1. 编写 makefile 2 ...

  10. MariaDB数据库----查(实例演示)

    MariaDB数据--查 SQl语句执行顺序 基础查询 查询 添加数据 MariaDB [test]> insert into huluwa values -> (1 ,'葫芦爷爷',73 ...