2020年04月06日17:12:29 重新看了一遍数位dp, 有了更深的理解:

假设我们先考虑一个简单的问题, 小于87, 且不包含1的的数字有多少,

我们这里先定义dp的含义, dp[i]表示长度为i+1的数字能组成多少种情况, 例如i=0时, 长度为1, 那总共有2,3,4,5,6,7,8,9中情况, 所以dp[0]=8, 那dp[1]则是dp[0]*8, 64种情况.

接下来我们考虑对于87, 如果十位为8, 个位的数字必须限制在小于等于7, 如果十位小于8, 则个位的数字可以任意取 (当然这些都不能取1, 1是不能包含的数字), 因此我们使用记忆化搜索

dfs(int p, bool limit) 表示从第p位开始能得到的结果, limit即表示该位是否能任意取, 当limit=0时, 返回的dfs值才是dp的值, 这也是为什么在返回或者赋值给dp时, 必须加上!limit的原因

前导0的情况

我们可以看到, 在最高位的时候, 我们是可以取到0的, 0即代表这一位没有, 但这里被默认填充上了0, 假设题目中要求不能填充0, 则我们必须允许前导0, 禁止后置的0. 加一个状态即可

一开始刷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)
 

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

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

2015-04-20 模板

 int dfs(int p,int s,bool e) {
if(p==-) return ;
if(!e &&dp[p][s]!=-) return dp[p][s];
int res= ;
int u=e?digit[p]:;
for (int i=;i<=u;++i)
{
if(i==||(s&&i==))
continue ;
res+=dfs(p-,i==,e&&i==u);
}
return e?res:dp[p][s]=res;
}
int solve(int n)
{
int len=;
while(n)
{
digit[len++]=n%;
n/=;
}
return dfs(len-,,);
}

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

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
 
那么就用这道题分析一下,首先放个源码
 #include <iostream>
using namespace std ;
int f[][] ;//f[i][0]:前i位符合要求 f[i][1]:前i位符合要求且i+1位是6
int digit[] ;//digit[i]表示n从右到左第i位是多少
int dfs(int i,int s,bool e)//i表示当前位,s表示i位之前的状态,e表示当前位是否可以随意填写
{
if(i==)
return ;
if(!e && f[i][s]!=-)
return f[i][s] ;
int res= ;
int u=e?digit[i]: ;
for(int d= ;d<=u ;d++)
{
if(d== || (s && d==))
continue ;
res+=dfs(i-,d==,e&&d==u) ;
}
return e?res:f[i][s]=res ;
}
int callen(int n)//计算n的长度
{
int cnt= ;
while(n)
{
cnt++ ;
n/= ;
}
return cnt ;
}
void caldigit(int n,int len)//计算n的digit数组
{
memset(digit,,sizeof(digit)) ;
for(int i= ;i<=len ;i++)
{
digit[i]=n% ;
n/= ;
}
}
int solve(int n)//计算[0,n]区间满足条件的数字个数
{
int len=callen(n) ;
caldigit(n,len) ;
dfs(len,,) ;
}
int main()
{
int n,m ;
memset(f,-,sizeof(f)) ;
while(~scanf("%d%d",&n,&m))
{
if(n== && m==)
break ;
printf("%d\n",solve(m)-solve(n-)) ;//用[0,m]-[0,n)即可得到区间[n,m]
}
return ;
}

2015-04-20 二次代码,风格变好了很多

 #include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
using namespace std;
#define MOD 1000000007
const int INF=0x3f3f3f3f;
const double eps=1e-;
#define cl(a) memset(a,0,sizeof(a))
#define ts printf("*****\n");
const int MAXN=;
int n,m,tt;
int digit[],dp[][];
int dfs(int p,int s,bool e) {
if(p==-) return ;
if(!e &&dp[p][s]!=-) return dp[p][s];
int res= ;
int u=e?digit[p]:;
for (int i=;i<=u;++i)
{
if(i==||(s&&i==))
continue ;
res+=dfs(p-,i==,e&&i==u);
}
return e?res:dp[p][s]=res;
}
int solve(int n)
{
int len=;
while(n)
{
digit[len++]=n%;
n/=;
}
return dfs(len-,,);
}
int main()
{
int i,j,k;
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
#endif
memset(dp,-,sizeof(dp));
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==&&m==)
break;
printf("%d\n",solve(m)-solve(n-));
}
}

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

 int dfs(int i,int s,bool e)//i表示当前位,s表示i位之前的状态(这里表示是否为6),e表示当前位是否可以随意填写
{
//printf("*****\n");
//printf("--%d %d %d\n",i,s,e);
if(i==)
return ; //说明前面的位数已经确定,该方案成立
if(!e && f[i][s]!=-)
{
//printf("--%d %d %d\n",i,s,e);
return f[i][s] ;
} int res= ;
int u=e?digit[i]: ;
//printf("--%d %d %d %d\n",i,s,e,u);
for(int d= ;d<=u ;d++)
{
if(d== || (s && d==))
continue ;
printf("--%d %d %d %d\n",i,s,e,d);
res+=dfs(i-,d==,e&&d==u) ;
}
printf("*************** %d %d %d %d\n",i,s,e,res);
return e?res:f[i][s]=res ;
}

这里输出了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

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

更多数位dp题可以参见我博客,代码风格基本是统一的

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

  1. 初探数位dp

    数位dp有着很明显的特点,一般来说是给定区间[l,r]求满足某种条件区间中的数有多少个 朴素解法一般是O(n)的而n往往很大(10^8起步) 这时候我们就要想办法优化,于是就有了数位dp 数位有两个基 ...

  2. [暑假集训--数位dp]hdu2089 不要62

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

  3. 数位DP HDU2089

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  4. 数位dp入门 hdu2089 不要62

    数位dp入门 hdu2089 不要62 题意: 给定一个区间[n,m] (0< n ≤ m<1000000),找出不含4和'62'的数的个数 (ps:开始以为直接暴力可以..貌似可以,但是 ...

  5. [bzoj1833][ZJOI2010]count 数字计数——数位dp

    题目: (传送门)[http://www.lydsy.com/JudgeOnline/problem.php?id=1833] 题解: 第一次接触数位dp,真的是恶心. 首先翻阅了很多很多一维dp,因 ...

  6. HDU2089 不要62[数位DP]

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  7. [您有新的未分配科技点]数位dp:从懵X到板子(例题:HDU2089 不要62)

    数位dp主要用来处理一系列需要数数的问题,一般套路为“求[l,r]区间内满足要求的数/数位的个数” 要求五花八门……比如“不出现某个数字序列”,“某种数的出现次数”等等…… 面对这种数数题,暴力的想法 ...

  8. hdu2089 数位dp

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

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

    题意:规定一个合法的号码不能含有4或者是连续的62 给定区间[n,m] 问此区间内合法的号码的个数 分析:数位dp dp[i][j]代表 最高位为 j 的 i 位数有多少个合法的 然后按题目规则进行转 ...

  10. 【数位DP】【HDU2089】不要62

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

随机推荐

  1. vtigerCRM5.4的安装和汉化 ubuntu

    由于php5.5经过较大改变,安装vtigercrm的时候可能需要修改一些配置 1.去官网下载vtiger5.4压缩包,解压后放在/var/www目录下 2.简单起见,将目录权限设置为777 vtig ...

  2. phpcms分页用法简介

    PHPCMS分页的用法 前面需要有引用的list,代码如下: {pc:content action="lists" catid="11" order=" ...

  3. xargs命令

    xargs命令 常用工具命令 xargs命令是给其他命令传递参数的一个过滤器,也是组合多个命令的一个工具.它擅长将标准输入数据转换成命令行参数,xargs能够处理管道或者stdin并将其转换成特定命令 ...

  4. CentOS6.5安装Tab增强版:bash-completion

    CentOS6.5安装Tab增强版:bash-completion,可补全命令参数: 因为CentOS官方源并不带有bash-completion的包,所以,为了可用yum安装,增加epel的源, 首 ...

  5. 常见的SQL语句

    1.select decode(a.xh,'','0','1')||decode(b.xh,'','0','1') from A a left join B b on a.xh=b.xh where ...

  6. 转: UAC 问题

    打开VS2005.VS2008.VS2010工程,查看工程文件夹中的Properties文件夹下是否有app.manifest这个文件:如 没有,按如下方式创建:鼠标右击工程在菜单中选择“属性”,点击 ...

  7. NPOI教程

    NPOI 是 POI 项目的 .NET 版本.POI是一个开源的Java读写Excel.WORD等微软OLE2组件文档的项目. NPOI 官方网站:http://npoi.codeplex.com/( ...

  8. ADO.NET中的五个主要对象

    Connection:主要是开启程序和数据库之间的连接.没有利用连接对象将数据库打开,是无法从数据库中取得数据的.Close和Dispose的区别,Close以后还可以Open,Dispose以后则不 ...

  9. ios 多线程必读内容 :锁

    大学时的生产者消费者问题还记得吗?ios中的锁,请阅读以下官方文档,虽然是英文的,但是说的非常准确: Threading Programming Guide 中的 Synchronization ht ...

  10. Tomcat内存溢出(java.lang.OutOfMemoryError: PermGen space)

    Tomcat启动时报如下错误:     java.lang.OutOfMemoryError: PermGen space 解决办法:     配置相关内存大小.其中按照启动tomcat的不同方式,分 ...