数位DP
可解决问题类型:
问题是给定了一个区间[A,B],符合题目给定的条件的数有多少。
条件一般和数的各数位的组成有关。
类似的题目如HDU 3555。
##方法思路
给定 N ( 1 < = N < = 2 63 − 1 ) N (1 <= N <= 2^{63}-1) N(1<=N<=263−1) 。求1~N之间,哪些数的数位包含49。如:N=50,那么其中的49的数位中出现了49。那么就计1。
比如我们找N=582的解。
我们决策的办法是记忆化+DFS,这就是数位DP。我们从最高位开始枚举,枚举1~5。
可以知道的是枚举1~3的得到的子问题的结果是相似的。因为它们都不包含49,并且,它们是完整的子问题,所以只需要计算一次1的子问题,我们就可以直接得到2和3的子问题答案,这就是记忆化的体现。
但是枚举4 和 5 的时候问题稍微不同。枚举4,我们需要注意,下一位也就是第一位不可以是9。其他的一样加起来。在枚举5的时候,就要注意下一位不可以全部都枚举到,也就是不可以枚举09。而是08,因为N的第二高位对枚举范围的限制。
- [04][08][0~9]
- 5[03][09] ^ 54[0~8] ^5[58][09]
- 58[0~2]
上面的是全部的类别。我们把子问题为[0~9]的答案存下来,留着下次遇到时候直接用,当前为4的时候的子问题的答案也单独存下来。那么需要重复计算的部分就很少了。
例如我先计算出0[09][09] 的答案,那么1[09][09] 的答案是不是就显而易见呢。但是5[03][09] ^ 54[0~8] ^5[58][09] 的答案就不可以直接得到了,需要再枚举。这样子就可以在 O ( n ) O(n) O(n) 的时间以内完成全部数位的枚举。具体细节操作我觉得这篇文章讲的不够好。可以看看视频。
重要的细节当然是一言难尽的。
这里的代码具有模板性。实际上是对HDU2089的题解。稍微改动就能运用到各种题目中。
练手题目
##参考推荐:
视频: 数位DP记录
博文 浅谈数位DP
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL ;
int digit[20];
LL dp[20][2];
LL dfs(int len ,bool if6,bool limit) {
if(len==0) return 1ll;
if(!limit && dp[len][if6]) return dp[len][if6];
int upperBound = (limit?digit[len]:9);
LL sum=0;
for(int i=0;i<=upperBound;i++) {
if((if6 && i==2 )|| i==4) continue;
sum+=dfs(len-1,i==6,limit && i==upperBound);
}
if(!limit) dp[len][if6]=sum;
return sum;
}
LL solve(LL n){
memset(dp,0,sizeof(dp));
int k=0;
while(n){
digit[++k]=n%10;
n/=10;
}
return dfs(k,false,true); ///对于最高位而言,就相当于他的上一位有限制
}
int main()
{
int T;
//scanf("%d",&T);
LL n,m;
while(scanf("%lld%lld",&n,&m)==2 && !(n==0 && m==0)) {
printf("%lld\n",solve(m)-solve(n-1));
}
return 0;
}