数位dp详解

哈哈哈,本菜鸡经过长时间研究,终于略懂的这看似神奇的数位dp。。。

数位dp,顾名思义就是数位+dp,数位即是一个数的每一位上的数字,dp也就是动态规划了。

首先来讲在何时应该想到要用数位dp吧。(相信大部分人都是为了做题而学的)

  数位dp的题目一般都是给定一个区间,如[l , r],然后叫你求在这区间里的数有多少个符合题目给的限制条件(解题时一般都是运用前缀和求解,即[l , r]=[0 , r]-[0 , l-1]。)

其次就是讲解数位dp的原理和模板代码了。(菜鸡的我不知道讲的好不好,尽量从一个初学者的角度来讲解吧。)

  1.数位dp的原理。

  数位dp是先将你要计算的最大上限按数位(即个、十、百、千,,,)拆分。然后按最高位去遍历每一个数(!!!注意,这里是用数位去遍历0~上限的每一位,其实也就是暴力枚举,不过它加上dp,就可以将时间复杂度降的十分低)。是不是觉得云里雾里,没看懂,没关系,看下面的一个样例你就明白了。

样例:找[35,156]中不包含6的数的个数。

首先将123拆成1、7、6;

这有个位,十位,百位三位。

百位:0 1 2 3 4 5 6 7 8 9

十位:0 1 2 3 4 5 6 7 8 9

个位:0 1 2 3 4 5 6 7 8 9

开始遍历:

百位是:

    0(0<1);

      十位是:0(0<7),那就能遍历到000,001,002,003,004,005,006,007,008,009。去掉前导0也就是0~9(在有些题目中是要处理掉前导0的,一般是与0有关的),因为6不符合题目条件,所以0~9有9个符合题目的数(将其记录下来);

      十位是:1(1<7),那就能遍历到010,011,012,013,014,015,016,017,018,019。去掉前导0也就是10~19,因为16不符合题目条件,所以0~9有9。(!!!注意:如果我们按这样一直遍历下去,那不就相当于暴力了,我们这是数位dp,数位已经在上面体现出来了,接下来就是dp上场了,我们在上一步是不是将0~9中符合题目条件的个数找出来了并且记录了它,那用你的聪明的大脑想想,我们在遍历十位上的数时是不是只要将结果搬过来就行?就如现在的十位是1,那结果不就是出去个位为6的数,因此就可以省去很多没必要的遍历);

      十位是:2(2<7),符合的个数是9(020,021,022,023,024,025,026,027,028,029);

      十位是:3(3<7),符合的个数是9(030,031,032,033,034,035,036,037,038,039);

      ...........4,5;

      十位是:6  (6<7),  这里因为十位是6,所以个位是什么都不符合。

      ...........7,8,9;

    1(1==1)

      十位是:0,1,2,3,4,5,6;个数是(9,9,9,9,9,9,0)

      十位是:7(7==7),!!!注意,因为上限是176,现在遍历到的是17X,这里这个X不能是(0~9)了,所以不能直接将9当作符合的个数,因此要一个一个数的去判断是不是我们要的数(即判断170,171,172,173,174,175,176是不是我们要的数)因此这只有6个;

结束遍历;

答案是9+9+9+9+9+9+0+9+9+9+9+9+9+9+9+9+0+6=ans1;

其次就是将35-1拆成3,4;

因为在遍历0~176是已经遍历了0~29,所以在进行遍历是0~29的符合个数会直接得到是9+9+9,而十位是3时个位只能是0~4,所以总个数是9+9+9+6=ans2;

因此我们的到了答案=ans1-ans2;

这里的数据比较小,但是如果数据是long long的范围,那是不是觉得会比平常的枚举快好多;

好了,样例我们已经讲完了,是不是觉得茅舍顿开,豁然开朗,那我们要怎么去用代码实现呢?接着往下看吧。

  2,模板代码讲解

    数位dp的实现方法很多,我一般都是喜欢用记忆化搜索实现(好吧,我其实只会用记忆化搜索实现);

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string.h>
 4 using namespace std;
 5 int dp[15][sta];//一维是当前第几位,sta是按题目所设的一种状态(不是必须有,比如上面例题没有也行),这也就是记忆化的数组;
 6 int disit[15];//储存拆分的数
 7 //***这是模板代码,所以sta不好写,但是没关系,你就把它先忽略,先理清它具体的写法就好了,等看完下面一个模板题你就明白了********
 8 int dfs(int len,......,bool limit)
 9 {
10     if(len==0)
11     return 1;
12     if(!limit&&dp[len][sta]!=-1)// 如上例中的176,如果不是百位是1并且十位是7的情况并且dp[len][sta]计算过时,就可直接返回符合个数
13     return dp[len][sta];
14     int Maxn=(limit?disit[len]:9);//找到可以遍历的最大数
15     int ans=0;
16     for(int i=0;i<=Maxn;i++)
17     {
18         if(.........)//这里可以根据题目加一些限制条件,可进行剪枝,根据题目而定;
19         ..........
20         .............
21         ans+=dfs(len-1,.....,limit&&i==disit[len]);//limit&&i==disit[len]是判断是否到了不能记忆化搜索的数;
22                                                     //如上例中的176,如果百位是1并且十位是7,那么就只能枚举剩下的数了;
23     }
24     if(!limit) dp[len][sta]=ans;//记忆化,当没有在上样例中的17X时,就可将计算的结果存入dp数组中进行记忆化;
25     return ans;
26 }
27 //****************拆数************
28 int solve(int x)
29 {
30     int len=0;
31     while(x)
32     {
33         disit[++len]=x%10;
34         x/=10;
35     }
36     return dfs(len,.....,true);
37 }
38 //*******************主函数一般都是这样,是不是超级简单*****************
39 int main()
40 {
41     //memset(dp,-1,sizeof(dp));
42     int l,r;
43     while(cin>>l>>r)
44     {
45         memset(dp,-1,sizeof(dp));//题目的限制条件在你计算答案时是固定的话,可以把它放外面,这样可以减少时间复杂度。
46         cout<<solve(r)-solve(l-1)<<endl;
47     }
48      return 0;
49 }

模板代码

 1 #include<iostream>
 2 #include<string.h>
 3 #define LL long long
 4 using namespace std;
 5 int disit[20];
 6 LL dp[20];
 7 LL dfs(int len,bool limit)
 8 {
 9     if(!len) return 1;
10     if(!limit&&dp[len]!=-1) return dp[len];
11     LL ans=0,Maxn=(limit?disit[len]:9);
12     for(int i=0;i<=Maxn;i++)
13     {
14         if(i==6)//当前位是6,则跳出,不搜索
15         continue;
16         ans+=dfs(len-1,limit&&i==Maxn);
17     }
18     if(!limit) dp[len]=ans;
19     return ans;
20 }
21 LL solve(LL x)
22 {
23     memset(disit,0,sizeof(disit));
24     int len=0;
25     while(x)
26     {
27         disit[++len]=x%10;
28         x/=10;
29     }
30     return dfs(len,true);
31 }
32 int main()
33 {
34     memset(dp,-1,sizeof(dp));
35     LL l,r;
36     while(cin>>l>>r)
37     {
38         cout<<(solve(r)-solve(l-1))<<endl;
39     }
40     return 0;
41 }

样例代码

模板题HDU2089  http://acm.hdu.edu.cn/showproblem.php?pid=2089

不要62

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 62447    Accepted Submission(s): 24776

Problem Description

杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

Input

输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。

Output

对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。

Sample Input

1 100

0 0

Sample Output

80

Author

qianneng

Source

迎接新学期——超级Easy版热身赛

Recommend

lcy

这题数据小,打表也行,不过万一是0<n≤m<1000000000000000呢,所以还是学会用数位dp做吧。

这里就可以用到dp的二维记录上一位数是否是6;

看代码吧

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string.h>
 4 using namespace std;
 5 int dp[15][2];
 6 int disit[15];
 7 int dfs(int len,bool sta_6,bool limit)//sta_6是记录上一位是否是6
 8 {
 9     if(!len) return 1;
10     if(!limit&&dp[len][sta_6]!=-1) return dp[len][sta_6];
11     int Maxn=(limit?disit[len]:9);
12     int ans=0;
13     for(int i=0;i<=Maxn;i++)
14     {
15         if(i==4)
16         continue;
17         if(sta_6&&i==2)
18         continue;
19         ans+=dfs(len-1,i==6,limit&&i==disit[len]);
20     }
21     if(!limit) dp[len][sta_6]=ans;
22     return ans;
23 }
24 int solve(int x)
25 {
26     int len=0;
27     while(x)
28     {
29         disit[++len]=x%10;
30         x/=10;
31     }
32     return dfs(len,false,true);
33 }
34 int main()
35 {
36     int l,r;
37     memset(dp,-1,sizeof(dp));
38     while(cin>>l>>r&&l+r)
39     {
40         cout<<solve(r)-solve(l-1)<<endl;
41     }
42     return 0;
43 } 

好了,终于讲完了,累死我了,有错的地方还请指正,能看到这的兄弟也不容易,数位dp中的dp是难点,不过多加训练还是能体会到其中的奥妙的,大家可以点进这里,一起训练和进步吧^-^。

https://www.cnblogs.com/liuzuolin/p/10348706.html

原文地址:https://www.cnblogs.com/liuzuolin/p/10333272.html

时间: 2024-08-08 16:15:23

数位dp详解的相关文章

数位dp详解&amp;&amp;LG P2602 [ZJOI2010]数字计数

数位dp,适用于解决一类求x~y之间有多少个符合要求的数或者其他. 例题 题目描述 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众. 不吉利的数字为所有含有4或62的号码.例如: 62315 73418 88914 都属于不吉利号码.但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列. 你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少

HDU 4049 Tourism Planning (状压dp 详解)

Tourism Planning Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1125    Accepted Submission(s): 487 Problem Description Several friends are planning to take tourism during the next holiday. Th

数位dp专题训练

数位dp详解:https://www.cnblogs.com/liuzuolin/p/10348718.html (内容会持续更新哦,大家一起学习吧) 原文地址:https://www.cnblogs.com/liuzuolin/p/10348706.html

【POJ3208】传说中POJ最难的数位DP?(正解AC自动机,二解数位DP,吾异与之)

题意: 多组数据,每组求第n个包含'666'的数(不能断开),如1:666,2:1666,14:6667. 题解: AC自动机解法没去想,数位DP没学,这里有一种类似于数位DP,却又与数位DP不同,我称为数位树. 数位树: 将数n如线段树一样地拆分成多个小段,进行递归处理得出答案. 本题详(lue)解: 直接看每一位应该是什么数,然后n减去相应的数,使得在下一层转换为子问题"在开头有b个连续的6时,求第a个带'666'的数".就是如此简单,如此简单!!!! 代码来啦! #include

(转)dp动态规划分类详解

dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间效率高,代码量少,多元性强,主要考察思维能力.建模抽象能力.灵活度. ****************************************************************************************** 动态规划(英语:Dynamic programm

HUD5282 Senior&#39;s String 详解(使用DP解决组合数学)

题意:假设两个字符串的最长公共子序列长度为L,求第一个字符串中有多少个长度为L的子序列是第二个字符串的子序列.显然找出一个字符串的所有长度为L的子序列是组合数学问题,如果枚举所有子串的时间复杂度是n! 级的.这里就需要用动态规划来解决.首先用dp[i][j]和num[i][j]分别记录x的前I个字母和y的前j 个字母的最长公共子序列的长度和个数.先求出dp, 然后求num:.求num[i][j]分为两种情况,子序列不选x[i]和选x[i]: 1. 不选x[i]: 如果dp[i][j] == dp

poj3252Round Numbers非递归数位dp解

Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 9894   Accepted: 3569 Description The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro, Sham, Bo'

hdu 5787 K-wolf Number 数位dp

数位DP 神模板 详解 为了方便自己参看,我把代码复制过来吧 // pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位) // status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回, // 给计数器+1. // limit = 是否受限,也即当前处理这位能否随便取值.如567,当前处理6这位, // 如果前面取的是4,则当前这位可以取0-9.如果前面取的5,那么当前 // 这位就不能随便取,不然会超出这个数的范围,所以如果前面取

LightOJ1068 Investigation(数位DP)

这题要求区间有多少个模K且各位数之和模K都等于0的数字. 注意到[1,231]这些数最大的各位数之和不会超过90左右,而如果K大于90那么模K的结果肯定不是0,因此K大于90就没有解. 考虑到数据规模,数据组数,这题状态这么表示: dp[i][j][k]:位数为i模K结果为j且各位数之和模K结果为k的数字个数 然后就是转移方程,最后就是统计.. 统计部分好棘手...半乱搞下AC的..还是对数位DP的这一部分太不熟悉了. 1 #include<cstdio> 2 #include<cstr