数位DP入门

数位DP顾名思义就是对数位运用DP思想,将每位数看成下一位的子状态,即个位看成十位的子状态,十位看成百位的子状态。下面给出状态方程:

dp[i][j]=dp[i-1][k](k=0,1,2,3...9)

dp[i][j] 表示i位数,首位是j的数字有多少符合要求的,我们从低位逐步递推求到高位。下面拿一道入门题来具体说明:HDU 2089 不要62传送门

如何求满足要求的种类数呢,我们先不考虑62和4的问题,那么  就拿335举例,当i<3时,就用递推公式,个位的各个数就是它自身,十位的各个数就等于个位的加和 即:dp[1][0]=1,dp[1][1]=1,dp[1][2]=1。。。。dp[2][0]等于个位的加和,即:dp[2][0]=10,dp[2][1]=10,dp[2][2]=10。。。。以此类推   通过dp[3][0],dp[3][1],dp[3][2]求得首位数字小于3的满足条件的三位数,dp[3][0]求得的数是001,052,093...也就是所有满足条件的一位或两位数……我们求得所有的2xx,1xx,0xx.

当i=3时,对应dp[3][3],但是满足要求的数有336,347,358……这些数超过了335……

首位只能是3,也就是第一位已经选完了,那么我们只要选择满足小于35的数字就可以了。和上面同理,我们只要求出dp[2][0],dp[2][1],dp[2][2],dp[2][3],dp[2][4]就可以求出首位小于3的两位数。这时求得的是32x,31x,30x。

然后我们求所有小于5的数字就ok。小于5也就是dp[0],dp[1],dp[2],dp[3],dp[4]。这时求得的是53x.

现在要考虑62和4的情况:

如果某一位数字为4,如77432的第三位(从左向右看)是4,那么我们依次求出6xxxx,5xxxx,...,0xxxx,76xxx,75xxx,...,70xxx,773xx,773xx,...,770xx,我们接来下需要求的是7742x....7740x,但这时所求的数是774xx的数,必然含有4,所以这时我们停止。

对于62,如果某一位数字是6,如5675,我们求完5xxx,4xxx,...0xxx之后再求56xx时,注意566x,565x,..,563x,561x,没有562x。如果数字中含有62,如5627,那么同4,求到562x的时候就应该停止继续求下去了。

下面附上代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[10][10];
int num[10];
int solve(int n){//求从0~n的满足条件的数
    int ans=0,len=0;
    int i,j;
    while(n){
        len++;
        num[len]=n%10;
        n/=10;
    }
    num[len+1]=0;
    for(i=len;i>=1;i--){
        for(j=0;j<num[i];j++){
            if(num[i+1]!=6||j!=2){
                ans+=dp[i][j];
            }
        }
        if(num[i]==4||(num[i+1]==6&&num[i]==2))
            break;
    }
    return ans;
}
int main(){
    int m,n,i,j,k;
    dp[0][0]=1;
    for(i=1;i<=7;i++){
        for(j=0;j<=9;j++){
            for(k=0;k<=9;k++){
                if(j!=4&&!(j==6&&k==2))//当上一位和这一位不同时为6、2时

                    dp[i][j]+=dp[i-1][k];
            }
        }
    }
    while(scanf("%d%d",&n,&m),m+n){
        printf("%d\n",solve(m+1)-solve(n));//由于求的是小于n的数,所以在[0,n]之间需要求到n+1
    }
    return 0;
}



递推公式
递推公式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值