☆ [HDU2089] 不要62「数位DP」
类型:数位DP
传送门:>Here<
题意:问区间[n,m]的数字中,不含4以及62的数字总数
解题思路
数位DP入门题
先考虑一般的暴力做法,整个区间扫一遍,判断每个数是否合法并累计答案。而数位DP则认为可以换一种方法来枚举,找到对于一个数的上限,然后在这个限度内枚举每一个数位来统计答案
为了方便数位DP,题意可以转化求区间[0,k]的符合要求的数字总数,因此答案就是ans(M)−ans(N−1)
首先我们可以预处理出dp数组:dp[i][j]表示以j开头的i位数的符合要求的数字总数;例如,dp[2][3]表示以3开头的2位数中符合要求的,也就是区间[30,39]中符合要求的。dp[i][j]=∑0≤k≤9 k≠4dp[i−1][k]这个方程很好理解,相当于枚举一个数位塞到前面,同时需要保证不能把6塞当2前面,并且特判一下4就好了
至于统计答案,我们从上限的最高位开始往下扫描。这里有个很巧妙的思想——每次处理不超过当前这一位的部分。形象地说,对于数字21358,最高位扫描0 1,也就是把答案累积上dp[5][0 1]。这一步相当于处理了区间[0,19999]中的所有;此时默认最高位是2,扫描到下一位,累积dp[4][0 0],也就相当于处理了区间[20000,20999];依次类推,然后将会处理[21000,21299],[21300,21349],[21350,21357]。因此我们可以在O(lgN∗10)的复杂度内处理区间[0,N−1]。(注意不包括N)ans=1∑i=numdigit[i]−1∑j=0dp[i][j]
然后在来看判断62和4的问题:每当我们进入到下一位,我们就将默认上一位确定。此时若确定的那一位为4,那么之后的都不用考虑了(一定不合法)。同理,如果当前确定的为2且上一位确定的为6,那么也可以跳出。事实上,这个跳出不是优化,而是必须那么做——如果不跳出,就会错误地累积很多答案。同时,不仅进入下一位的时候要判断,扫描的时候也要判断。道理一样
拓展:如果题目要求的不是【不要62】而是【要62】呢?就好像[HDU3555] Bomb所要求的一样,只需要求出所有的【不要62】数字,用N减一下就好了
Code
特别需要注意的是digit[num+1]==0这一步的处理,如果不加这一步,那么如果在处理前一个数字时残留下了digit[num+1]==6,那么你的程序将不能在最高位填充2了!
另外还有dp数组的初始化问题:一种是dp[0][0]=1,或者对于所有i≠4,dp[1][i]=1。其实这两者是等效的,因为在统计dp[1][i]时,只会累积到dp[0][0]为1
/*By DennyQi 2018.8.13*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 27010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w; } int N,M,num,X,Y; int dp[10][11],digit[10]; inline void Init(){ dp[0][0] = 1; for(int i = 1; i <= 7; ++i){ for(int j = 0; j <= 9; ++j){ if(j == 4) continue; for(int k = 0; k <= 9; ++k){ if(k == 2 && j == 6) continue; dp[i][j] += dp[i-1][k]; } } } } inline int cul(int x){ num = 0; int y = x, res = 0; while(y > 0){ digit[++num] = y % 10; y /= 10; } digit[num+1] = -1; for(int i = num; i >= 1; --i){ for(int j = 0; j < digit[i]; ++j){ if(digit[i+1] == 6 && j == 2) continue; res += dp[i][j]; } if(digit[i] == 4 || (digit[i+1]==6&&digit[i]==2)) break; } return res; } int main(){ Init(); for(;;){ N = r, M = r; if(!N && !M) break; printf("%d\n",cul(M+1)-cul(N)); } return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战