初探数位DP-hdu2089

一开始刷dp就遇到了数位dp,以前程序设计艺术上看过一点,基本没懂,于是趁今天遇到题目,想把它搞会,但就目前状态来看仍然是似懂非懂啊,以后还要反复搞

统计区间[l,r]的满足题意的数的个数,可以转换成求[0,r]-[0,l),这也是数位dp题的一个明显的提示

F[i,st] 代表 位数为i(可能允许前导0。如00058也是个5位数),状态为st的方案数。这里st根据题目需要确定。

如i=4,f[i,st]也就是0000~9999的符合条件的数的个数(十进制)

决策第i位是多少(such as 0~9)

这里采用的是记忆化搜索的处理方式,有模板

1 int dfs(int i, int s, bool e) {                 //i表示当前的位数,s表示状态,e表示后面位数能否任意填
2     if (i==-1) return s==target_s;              //最后一位取完,找到一个符合条件的值
3     if (!e && ~f[i][s]) return f[i][s];         //之前位数对应要求的值已经确定,在这里就直接返回
4     int res = 0;                                //记录符合条件的值
5     int u = e?num[i]:9;                         //是否能任意填,能任意填则必须小于原来位数上对应的值,否则则可以去到0-9
6     for (int d = first?1:0; d <= u; ++d)        //逐个填充值,通常会在下面继续加上一些条件,排除不需要的值
7         res += dfs(i-1, new_s(s, d), e&&d==u);  //下个位数
8     return e?res:f[i][s]=res;                   //可以任意填的话,说明到i位还未确定res没有包含所有情况,不可以任意填说明后面已经确定,即f也可以确定
9 }

正确与否有待进一步确认,第一遍看就暂且这么理解吧

f为记忆化数组;

i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。

今天做了一道基础数位dp题,来自hdu2089

题目大意:给定区间[n,m],求在n到m中没有“62“或“4“的数的个数。

如62315包含62,88914包含4,这两个数都是不合法的。0<n<=m<1000000

那么就用这道题分析一下,首先放个源码

 1 #include <iostream>
 2 using namespace std ;
 3 int f[8][2] ;//f[i][0]:前i位符合要求 f[i][1]:前i位符合要求且i+1位是6
 4 int digit[9] ;//digit[i]表示n从右到左第i位是多少
 5 int dfs(int i,int s,bool e)//i表示当前位,s表示i位之前的状态,e表示当前位是否可以随意填写
 6 {
 7     if(i==0)
 8         return 1 ;
 9     if(!e && f[i][s]!=-1)
10         return f[i][s] ;
11     int res=0 ;
12     int u=e?digit[i]:9 ;
13     for(int d=0 ;d<=u ;d++)
14     {
15         if(d==4 || (s && d==2))
16             continue ;
17         res+=dfs(i-1,d==6,e&&d==u) ;
18     }
19     return e?res:f[i][s]=res ;
20 }
21 int callen(int n)//计算n的长度
22 {
23     int cnt=0 ;
24     while(n)
25     {
26         cnt++ ;
27         n/=10 ;
28     }
29     return cnt ;
30 }
31 void caldigit(int n,int len)//计算n的digit数组
32 {
33     memset(digit,0,sizeof(digit)) ;
34     for(int i=1 ;i<=len ;i++)
35     {
36         digit[i]=n%10 ;
37         n/=10 ;
38     }
39 }
40 int solve(int n)//计算[0,n]区间满足条件的数字个数
41 {
42     int len=callen(n) ;
43     caldigit(n,len) ;
44     dfs(len,0,1) ;
45 }
46 int main()
47 {
48     int n,m ;
49     memset(f,-1,sizeof(f)) ;
50     while(~scanf("%d%d",&n,&m))
51     {
52         if(n==0 && m==0)
53             break ;
54         printf("%d\n",solve(m)-solve(n-1)) ;//用[0,m]-[0,n)即可得到区间[n,m]
55     }
56     return 0 ;
57 }

这里通过输出中间变量来辅助理解

 1 int dfs(int i,int s,bool e)//i表示当前位,s表示i位之前的状态(这里表示是否为6),e表示当前位是否可以随意填写
 2 {
 3     //printf("*****\n");
 4     //printf("--%d %d %d\n",i,s,e);
 5     if(i==0)
 6         return 1 ;      //说明前面的位数已经确定,该方案成立
 7     if(!e && f[i][s]!=-1)
 8     {
 9         //printf("--%d %d %d\n",i,s,e);
10         return f[i][s] ;
11     }
12
13     int res=0 ;
14     int u=e?digit[i]:9 ;
15     //printf("--%d %d %d %d\n",i,s,e,u);
16     for(int d=0 ;d<=u ;d++)
17     {
18         if(d==4 || (s && d==2))
19             continue ;
20         printf("--%d %d %d %d\n",i,s,e,d);
21         res+=dfs(i-1,d==6,e&&d==u) ;
22     }
23     printf("***************  %d %d %d %d\n",i,s,e,res);
24     return e?res:f[i][s]=res ;
25 }

这里输出了0-200的情况

1 200
--3 0 1 0  //从百位开始计算,之前没有6,所以中间为0,后面可以任意填充所以为1,首先在第一位上填0
--2 0 0 0  //第二位上填0
--1 0 0 0  //第三位上填0,一种情况,res+1,下面一样
--1 0 0 1  //第三位上填1
--1 0 0 2
--1 0 0 3
--1 0 0 5
--1 0 0 6
--1 0 0 7
--1 0 0 8
--1 0 0 9  //第三位上填9
***************  1 0 0 9  //f[1][0]=9,个位上的情况且十位不含6全部确定共9种,下一步之前res重新清零
--2 0 0 1  //十位填2,之后再确定个位,发现个位上的情况已经确定,于是直接返回f[1][0],res+f[1][0]
--2 0 0 2
--2 0 0 3
--2 0 0 5
--2 0 0 6  //十位填6,之后s变为1,个位需要重新确定
--1 1 0 0
--1 1 0 1
--1 1 0 3
--1 1 0 5
--1 1 0 6
--1 1 0 7
--1 1 0 8
--1 1 0 9
***************  1 1 0 8  f[1][1]=8  //个位上十位含6共9种情况
--2 0 0 7  //继续枚举十位
--2 0 0 8
--2 0 0 9
***************  2 0 0 80   //f[2][0]=80
--3 0 1 1  //百位填1  ret+f[2][0]
--3 0 1 2  //百位填2  ret+f[2][0]
--2 0 1 0  //十位填0
--1 0 1 0  //个位填0  ret+1
***************  1 0 1 1  //个位上只能有0
***************  2 0 1 1  //十位上只能有00
***************  3 0 1 161  //返回的是ret
161

正确性有待商榷,待我再多做几道题看看

时间: 2024-08-07 04:33:49

初探数位DP-hdu2089的相关文章

初探数位dp

前言:这是蒟蒻第一次写算法系列,请诸位大佬原谅文笔与排版. 一.导入 在刷题的时候,我们有时会见到这样一类问题:在区间$[l,r]$内,共有多少个整数满足某种条件.如果$l$和$r$间的差很小,我们可以考虑暴力枚举直接判断.然而,若$l<=r<=10^9$甚至更大呢? 这时往往就可以用到一种dp方式:数位dp. 二.做法: 这里先放一道例题:1026: [SCOI2009]windy数. 题意:求在区间$[l,r]$内,满足相邻数位的差>=2的整数的个数. 首先我们可以发现,$[l,r]

HDU2089 不要62[数位DP]

不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 36862    Accepted Submission(s): 13418 Problem Description 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可

hdu2089不要62(数位dp)

题目大意就是数字中不能出现 62 和4 代码: /************************************************************************* > File Name: 2089.cpp > Author: minshik > Created Time: 2014年05月22日 星期四 00时52分49秒 *****************************************************************

【数位dp】hdu2089 不要62

好吧,虽然是道水题但是是第一次接触数位dp所以还是要记录一下. 其实就是因为如果按数的大小枚举的话很不方便所以就按数位枚举并进行记忆化. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define s(x,y) scanf("%d%d",&x,&y) #define p(x) p

hdu2089(数位DP 递推形式)

不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 25802    Accepted Submission(s): 8967 Problem Description 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以

hdu2089(数位dp)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:求区间[a,b]内不含有62或4的数的个数. 分析:数位dp,dp[pos][0]表示到第pos位还没含有62或4,dp[pos][1]表示到第pos位前一位是6,dp[pos][2]表示已包含4或62. #include <cstdio> #include <cstring> #include <string> #include <cmath> #

数位dp初探

我这种蒟蒻就一直不会写数位dp.. 于是开了个坑.. 1833: [ZJOI2010]count 数字计数 这道被KPM大爷说是入门题..嗯似乎找找规律然后减掉0的情况后乱搞就可以了..(但是还是写了很久TAT #include<cstring> #include<iostream> #include<cstdio> #include<queue> #include<cmath> #include<algorithm> #define

HDU2089 ------不要62(数位dp)

不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 26083    Accepted Submission(s): 9085 数位dp大法好! . Problem Description 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer). 杭州交通管理局常常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利

hdu2089不要62(数位dp)

不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 43588    Accepted Submission(s): 16034 Problem Description 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可

hdu2089:不要62(基础数位dp)

题意:规定一个合法的号码不能含有4或者是连续的62 给定区间[n,m] 问此区间内合法的号码的个数 分析:数位dp dp[i][j]代表 最高位为 j 的 i 位数有多少个合法的 然后按题目规则进行转移即可 dp结束后,再统计范围内的总数,最后打表输出 代码: #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define MAX 100000000 int n,