数位dp

数位DP

可解决问题类型:

问题是给定了一个区间[A,B],符合题目给定的条件的数有多少。

条件一般和数的各数位的组成有关。

类似的题目如HDU 3555。

##方法思路

给定 N ( 1 < = N < = 2 63 − 1 ) N (1 <= N <= 2^{63}-1) N(1<=N<=2631 。求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的第二高位对枚举范围的限制。

  1. [04][08][0~9]
  2. 5[03][09] ^ 54[0~8] ^5[58][09]
  3. 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的题解。稍微改动就能运用到各种题目中。

练手题目

HDU 3555

不要62

##参考推荐:

视频: 数位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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值