数位DP模板详解

数位DP模板详解已经很长时间没有做过关于数位 DP 的题目了 现在来写一下自己对于数位 DP 的理解 一般这种题目都是问在区间 l r 内满足某种条件的数有多少 显然我们可以转换为求 0 x 中满足该条件的数有多少 然后利用前缀和思想 直接用 0 r 中满足某种条件的数的个数减去 0 l 1 中满足某种条件的个数即可 这个就不细说了 下面看一下板子 inta N llf N s 第一维一般是当前枚举到的位数 第二位表示状态 具体问题具体分析 lldp intpos 当前枚举到的位 s 代表状态 b

已经很长时间没有做过关于数位DP的题目了,现在来写一下自己对于数位DP的理解:

一般这种题目都是问在区间[l,r]内满足某种条件的数有多少,显然我们可以转换为求0~x中满足该条件的数有多少,然后利用前缀和思想,直接用0~r中满足某种条件的数的个数减去0~l-1中满足某种条件的个数即可,这个就不细说了,下面看一下板子:

int a[N]; ll f[N][s];//第一维一般是当前枚举到的位数,第二位表示状态(具体问题具体分析) ll dp(int pos/*当前枚举到的位*/,/*s代表状态*/,bool lead/*前导0,有些题目需要考察前导0*/,bool limit/*当前位是否可以任选*/) { //递归边界 if(pos==0) return /*满足条件?1:0*/; if(!limit&&!lead&&f[pos][s]!=-1) return f[pos][s];//记忆化,在没有前导0且可以任选的情况下如果当前状态的值已经被计算过则可以直接返回 int up=limit?a[pos]:(进制-1);//根据limit判断枚举的上界up,如果前面一直都是取的可以取的最大值,那么当前值最大也是所给数该位的值,反之为要求进制下所能取到的最大值 ll ans=0;//记录答案 for(int i=0;i<=up;i++)//枚举每一位并记录答案 { if() return 0;//这个地方有可能会剪枝 //这个地方有可能会因为前导0的存在而分类讨论 ans+=dp(pos-1,/*状态转移*/,lead&&i==0,limit&&i==a[pos]);//这个仔细想想也比较容易理解 } if(!limit&&!lead) f[pos][s]=ans;//如果当前可以任选且没有前导0那么就把答案记录下来用于记忆化搜索 return ans; } ll solve(ll x) { if(!x) return ;//一般需要单独讨论x取0的情况,否则有可能在数位DP过程中出现一些不可预知的问题 int pos=0; while(x)//把数拆分至a数组中 { a[++pos]=x%进制; x/=进制; } return dp(pos/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始默认为有限制且有前导0 } int main() { ll l,r; memset(f,-1,sizeof f);//初始化 cin>>l>>r; printf("%lld\n",solve(r)-solve(l-1)); return 0; }

对于limit的理解:假如我们当前遍历到第pos位,那么当前位置的取值有两种可能性:

(1)如果pos前面某一位已经小于上限数字对应位置的数字,这一位可以填0~进制数-1

(2)如果pos前面每一位都等于上限数字对应位置的数字,这一位可填充数字范围位0~上限数字对应位置的数字

而limit就是维护前面每一位是否和上限数字一样,就可以得到当前位置数字可填范围,也是可以进行记忆化搜索的条件

那为什么记忆化搜索一定要在可以任选的情况下进行呢?下面看下递归语句

ans+=dfs(pos-1,/*状态转移*/,lead&&i==0,limit&&i==a[pos]);

如果可以任选了,首先有lead=0,这个很显然,都可以任选了,说明前面一定有一位不是上界,而且limit=0,这样在之后的所有可选数上限都会是进制数-1,这样的状态上限是唯一的,而且是容易存储的,所以当这样的结果已经被保存下来就可以直接作为结果返回而不需要再次进行搜索,这就是dp的记忆化搜索部分,也是精髓

下面我来说一下前导0的影响:

有些题目需要考虑前导0,而有些题目不需要考虑前导0(这主要取决于答案的数量是否与0有关)

先说一下什么是前导0,比如x=9999,小于x的数包括一位数,二位数,三位数,四位数,由于我们数位DP是按照位来枚举的,所以一位数3默认为0003,但这属于包含了前导0

需要考虑前导0的题目一般是结果与0有关的,举个例子,假如我们要求[l,r]中有多少个数满足二进制表示中0的个数大于等于1的个数,显然我们记忆化数组中需要记录0的和1的个数,但是这个时候前导0就会对答案产生影响了,因为前导0并不是真实存在的,只是为了我们方便表示一个数来设置的,他不应该被计入当前数的二进制表示中0的个数,所以就需要我们在搜索过程中单独处理一下,当然也有一些题目是不会被前导0影响的,比如不要62,意思就是求[l,r]中满足十进制表示中不包含4和“62”的数有多少,这个显然与0无关,所以他不会受前导0的影响,我们只需要记录当前位前一位即可进行记忆化搜索。

下面我给出这两道题目的链接及代码:

3252 — Round Numbers

代码:

#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> using namespace std; typedef long long ll; const int N=35; ll f[N][N][N];//f[i][j][k]表示遍历到第i位有j个0以及k个1的合法方案数 ll a[N]; ll dp(int pos,int num0,int num1,int lead,int limit) { if(!pos) return num0>=num1; if(!lead&&!limit&&f[pos][num0][num1]!=-1) return f[pos][num0][num1]; int up=limit?a[pos]:1; ll ans=0; for(int i=0;i<=up;i++) { if(lead)//有前导0 ans+=dp(pos-1,num0,num1+(i!=0),lead&&(i==0),limit&&i==up); else ans+=dp(pos-1,num0+(i==0),num1+(i!=0),lead,limit&&i==up); } if(!lead&&!limit) f[pos][num0][num1]=ans; return ans; } ll solve(ll x) { int pos=0; if(!x) return 1; while(x) { a[++pos]=x%2; x/=2; } return dp(pos,0,0,1,1); } int main() { ll l,r; memset(f,-1,sizeof f); cin>>l>>r; cout<<solve(r)-solve(l-1); return 0; } 

不要62(杭电进不去了,我就放上题意吧)

题意:

Input

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

Output

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

Sample Input

80

代码:

#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> using namespace std; const int N=203; typedef long long ll; ll f[N][10],a[N]; ll dp(int pos,int last,int limit) { if(!pos) return 1; if(!limit&&f[pos][last]!=-1) return f[pos][last]; int up=limit?a[pos]:9; ll ans=0; for(int i=0;i<=up;i++) { if(last==6&&i==2) continue;//剪枝 if(i==4) continue;//剪枝 ans+=dp(pos-1,i,limit&&(i==up)); } if(!limit) f[pos][last]=ans; return ans; } ll solve(ll x) { if(!x) return 1; int pos=0; while(x) { a[++pos]=x%10; x/=10; } return dp(pos,0,1); } int main() { ll l,r; memset(f,-1,sizeof f); while(scanf("%lld%lld",&l,&r)&&(l||r)) { printf("%lld\n",solve(r)-solve(l-1)); } return 0; }

通过这道多组输入题我还想给大家说一个技巧,就是一个数是否满足题目中要求的条件是一定的,不会随着查询区间的变化而变化,所以我们在对f数组进行初始化时只需要在最开始初始化一次即可。

这就是我对数位DP的理解了,在之后我还会更新一些有关数位DP的题目,希望能够帮助到大家!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233869.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 数据挖掘的9大成熟技术和应用

    数据挖掘的9大成熟技术和应用http://ihoge.cn/2018/DataMining.html数据挖掘的9大成熟技术和应用基于数据挖掘的9大主要成熟技术以及在数据化运营中的主要应用:1、决策树2、神经网络3、回归4、关联规则5、聚类6、贝叶斯分类7、支持向量机8、主成分分析9、假设检验1 决策树决策树(DecisionTree)是一种非常成熟的、普遍采用的数据挖…

    2022年6月15日
    45
  • debounce实现 js_javascript防抖函数debounce详解「建议收藏」

    debounce实现 js_javascript防抖函数debounce详解「建议收藏」定义及解读防抖函数debounce指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次。假如我们设置了一个等待时间3秒的函数,在这3秒内如果遇到函数调用请求就重新计时3秒,直至新的3秒内没有函数调用请求,此时执行函数,不然就以此类推重新计时。举一个小例子:假定在做公交车时,司机需等待最后一个人进入后再关门,每次新进一个人,司机就会把计时器清零并重新开始计时,重新等…

    2022年6月20日
    54
  • LTE TDD与LTE FDD技术简介和比较

    LTE TDD与LTE FDD技术简介和比较摘要:UTRA的长期演进(LongTermEvolution,LTE)技术存在LTEFDD和LTETDD两大阵营,本文在比较分析TDD和FDD技术特点的基础上,对LTETDD(即TD-LTE)的特有技术进行了总结,并结合中国移动现有的网络部署和TDD频段资源情况,对LTETDD和LTEFDD的应用前景进行了初步分析。1、引言        随着移动通信技术的蓬勃

    2022年5月29日
    48
  • idea如何配置数据库连接_idea配置数据库驱动

    idea如何配置数据库连接_idea配置数据库驱动学习时,使用IDEA的时候,需要连接Database,连接时遇到了一些小问题,下面记录一下操作流程以及遇到的问题的解决方法。idea连接数据库教程目录一、连接操作1.1创建连接1.2连接数据库1.3查看检验1.3.1在终端上检验1.3.2在Navicat上检验二、解决问题一、连接操作简介:介绍如何创建连接,具体连接某个数据库的操作流程。1.1创建连接打开idea…

    2025年12月15日
    8
  • Spring batch批量处理框架最佳实践

    Spring batch批量处理框架最佳实践springbatch精选,一文吃透springbatch批量处理框架前言碎语批处理是企业级业务系统不可或缺的一部分,springbatch是一个轻量级的综合性批处理框架,可用于开发企业信息系统中那些至关重要的数据批量处理业务.SpringBatch基于POJO和Spring框架,相当容易上手使用,让开发者很容易地访问和利用企业级服务.springbatch具有高可扩展性的框架…

    2022年5月23日
    36
  • 防止自己服务器变矿机的软件_服务器被挖矿了怎么办

    防止自己服务器变矿机的软件_服务器被挖矿了怎么办0x00背景周一早上刚到办公室,就听到同事说有一台服务器登陆不上了,我也没放在心上,继续边吃早点,边看币价是不是又跌了。不一会运维的同事也到了,气喘吁吁的说:我们有台服务器被阿里云冻结了,理由:对外恶意发包。我放下酸菜馅的包子,ssh连了一下,被拒绝了,问了下默认的22端口被封了。让运维的同事把端口改了一下,立马连上去,顺便看了一下登录名:root,还有不足8位的小白密码,心里一凉:被…

    2022年9月26日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号