HDU-3652 B-number (数位DP)

本文详细介绍了如何使用数位动态规划算法解决B-number计数问题,即在给定范围内找出所有包含'13'且能被13整除的数字数量。通过构建状态转移方程,实现高效计算。同时,提供了两种方法的实现代码,包括循环和递归数位DP,供读者学习和参考。

B-number

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)


Problem Description
A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string "13" and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task is to calculate how many wqb-numbers from 1 to n for a given integer n.
 

Input
Process till EOF. In each line, there is one positive integer n(1 <= n <= 1000000000).
 

Output
Print each answer in a single line.
 

Sample Input
  
13 100 200 1000
 

Sample Output
  
1 1 2 2

题目大意:求区间[1,n]中含13且是13的倍数的数字个数?


方法一:DP(循环)

感觉入错坑了...非递归的数位DP好难写(虽然一次AC了),但是一看别人的递归数位DP,非常简洁,还不容易出错,需要赶快换一下


本题比原先做的又多了一个限制:数还要满足是13的倍数,然后就又不会了...

看了题解的设置状态的部分,瞬间明白了,可以再加一维表示余数的状态,然后利用模运算的性质就能顺利成章地想到以下这种转移状态(又看到有非递归的题解,感觉还是写的麻烦了)

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n,bit[35],len,ans;
int dp[35][13][3];//dp[i][j][0]表示长度为i,模13后为j且不含13的数字个数;dp[i][j][1]表示长度为1,模13后为j且不含13,第i位为3的数字个数;dp[i][j][2]表示长度为i,模13后为j且含有13的数字个数


void Init() {
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1;
    for(int i=1,hig=1;i<=31;++i,hig*=10) {//枚举第i位,权值为hig
        for(int num=0;num<=9;++num) {//枚举第i位的数字
            for(int rem=0;rem<13;++rem) {//枚举低i-1位模13的值
                int cur=(num*hig+rem)%13;//cur表示第i位为num,低i-1位模13为rem时,低i位模13的值
                dp[i][cur][0]+=dp[i-1][rem][0];
                if(num==1) {//如果第i位为1,则需要减去第i-1位为3的情况
                    dp[i][cur][0]-=dp[i-1][rem][1];
                }

                if(num==3) {//如果第i位为3,则低i-1位中所有不含13的状态都符合
                    dp[i][cur][1]+=dp[i-1][rem][0];
                }

                dp[i][cur][2]+=dp[i-1][rem][2];
                if(num==1) {//如果第i位为1,且第i-1位为3,则也符合当前状态
                    dp[i][cur][2]+=dp[i-1][rem][1];
                }
            }
        }
    }
}

int getCnt(int x) {//返回区间[1,x]中含13且是13的倍数的数字个数
    ++x;
    int hx=0,tmp;//hx表示从i+1位开始的高位的值(不含低i位的权值);tmp表示从i位开始的高位的值(含第i-1位的权值)
    int weight=1;//weight表示第i位的权值
    bool flag=false;
    ans=len=0;
    while(x>0) {
        bit[++len]=x%10;
        x/=10;
        weight*=10;
    }
    bit[len+1]=-1;
    weight/=10;

    for(int i=len;i>=1;--i,weight/=10) {//从高到低枚举每一位
        for(int num=0;num<bit[i];++num) {//枚举第i位可取到的数字(该位后面的位的所有状态均能取到)
            tmp=(hx*10+num)*weight;
            for(int rem=0;rem<13;++rem) {//枚举低i-1位模13后的值
                if((tmp+rem)%13==0) {//当前状态下,所有是13的倍数
                    ans+=dp[i-1][rem][2];//这些数中低i-1位中含有13的数

                    if(flag) {//如果高位已经出现过13
                        ans+=dp[i-1][rem][0];//这些数中低i-1位中不含13的数
                    }
                    else {//如果高位中没出现过13
                        if(bit[i+1]==1&&num==3) {//如果第i+1位为1,且第i位取3时,低i-1位可取不含13的状态
                            ans+=dp[i-1][rem][0];
                        }
                        if(num==1) {//如果当前位可取的数大于1,低i-1位中可取第i-1为3但不含13的状态
                            ans+=dp[i-1][rem][1];
                        }
                    }
                }
            }
        }
        if(bit[i+1]==1&&bit[i]==3) {//如果第i+1位为1且第i位为3,则高位中必定含有13
            flag=true;
        }
        hx=hx*10+bit[i];
    }
    return ans;
}

int main() {
    Init();
    while(1==scanf("%d",&n)) {
        printf("%d\n",getCnt(n));
    }
    return 0;
}


方法二:DP(递归)


递归太好写了,需要考虑的限制非常少,决定以后就专弄递归的方法吧

#include <cstdio>
#include <cstring>

using namespace std;

int bit[35],len;
int dp[35][13][3];//dp[i][j][0]表示长度为i,模13后为j且不含13的数字个数;dp[i][j][1]表示长度为1,高位的数模13后为j且不含13,第i位为3的数字个数;dp[i][j][2]表示长度为i,模13后为j且含有13的数字个数

int dfs(int pos,int pre,int rem,bool limit,bool flag) {//pos表示当前考虑的位置,pre前一位的数,rem高位的数的模13的值,limit当前位取的数是否有限制,flag前面是否出现过13
    if(pos<=0) {
        return flag&&rem==0?1:0;//如果前面出现过13,且余数为0,则符合题意,为1;否则不符合题意,为0
    }
    if(!limit) {//如果没有限制,就可以直接统计后面的所有位的情况
        if(flag) {//如果前面已经出现过13,则后面所有的位任取不含13的
            if(dp[pos][rem][0]!=-1) {//如果计算过就直接返回
                return dp[pos][rem][0];
            }
        }
        else {//如果前面没出现过13
            if(pre==1&&dp[pos][rem][1]!=-1) {//如果上一位是1,则后面的最高位取3,若计算过就直接返回
                return dp[pos][rem][1];
            }
            if(pre!=1&&dp[pos][rem][2]!=-1) {//如果上一位不是1,则后面取含13的数,若计算过就直接返回
                return dp[pos][rem][2];
            }
        }
    }

    int res=0,mx=limit?bit[pos]:9;//mx表示当前位最大能取的数
    for(int i=0;i<=mx;++i) {//枚举当前位的数字
        res+=dfs(pos-1,i,(rem*10+i)%13,limit&&i==mx,flag||(pre==1&&i==3));
    }

    if(!limit) {//此时保存刚才算得的结果
        if(flag) {
            dp[pos][rem][0]=res;
        }
        else {
            if(pre==1) {
                dp[pos][rem][1]=res;
            }
            else {
                dp[pos][rem][2]=res;
            }
        }
    }

    return res;
}

int getCnt(int n) {
    len=0;
    while(n>0) {
        bit[++len]=n%10;
        n/=10;
    }
    return dfs(len,0,0,true,false);
}

int main() {
    int n=0;
    memset(dp,-1,sizeof(dp));
    while(1==scanf("%d",&n)) {
        printf("%d\n",getCnt(n));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值