数位DP入门

对于位数比较多的数,这样的过程中有许多重复的部分。例如,从 7000 数到 7999、从 8000 数到 8999、和从 9000 数到 9999 的过程非常相似,它们都是后三位从 000 变到 999,不一样的地方只有千位这一位,所以我们可以把这些过程归并起来,将这些过程中产生的计数答案也都存在一个通用的数组里。此数组根据题目具体要求设置状态,用递推或 DP 的方式进行状态转移。
统计答案可以选择记忆化搜索,也可以选择循环迭代递推。为了不重不漏地统计所有不超过上限的答案,要从高到低枚举每一位,再考虑每一位都可以填哪些数字,最后利用通用答案数组统计答案。

例题1

1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。对于所有评测用例,1 ≤ N ≤ 1018, 1 ≤ K ≤ 50。

对于N=7 K=2,二进制为111。
1、从最高位开始选,选0时0 _ _ ,最大值为011不会超过最大值,直接加上后两位可以全排列C(2, 2)。选1时1 _ _ 记录1的个数。
2、选第二位,选0时10 _ ,直接加上后一位的全排列 C(1, 2-1);选1时1的个数加1。
3、选最后一位11_ ,前面1的个数2 == k,答案加1。所以最后答案为C(2, 2) + C(1, 1) + 1 = 3

#include <iostream>

using namespace std;

typedef unsigned long long LL;

const int N = 64, M = 64;

LL n, k, c[N][N];

int main(){
    for(int i=0; i<=N; i++){          //组合数公式,cij即i中选j个的个数
        c[i][0] = 1;
        for(int j=1; j<=i; j++)
            c[i][j] = c[i-1][j-1] + c[i-1][j];
    }   
    cin >> n >> k;
    LL ans = 0, cnt = 0;
    int a[M], st = 0;
	while(n){                        //取出每一位数字
        a[++st] = n&1;
        n = n>>1;
    }
    for(int i=st; i>0; i--){         //从高位到低位统计每一位数字可能出现的次数
        if(i==1){                    //单独处理最后一位
            if(cnt==k || cnt+a[i]==k)//
                ans++;
        }
		else if(a[i]){               //当前位为1
            ans += c[i-1][k-cnt];    //选0时,那么剩下的i-1位可以全排列
            ++cnt;                   //选1时,统计已选的1的个数
        } 
    }
    printf("%lld",ans);
    return 0;
}

例题2

给定两个正整数 aa 和 bb,求在 [a,b][a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

递推实现

#include <iostream>
#include <cmath>
using namespace std;

typedef long long LL;

const int N = 14;

LL a, b, cnta[N], cntb[N], dp[N];

void f(LL x, LL cnt[N]){//统计1~x之间每个数字出现的个数,并存储到cnt数组中
    int dig[N], len = 0;//存储每一位上的数字
    LL d = x;
    do{//只有0也会统计成一位
        dig[++len] = d % 10;
        d /= 10;
    }while(d);
    
    for(int i=len; i; i--){//从高位往低位遍历,求出当前位所有数字出现的情况
        for(int j=0; j<10; j++)//当前位不选最大值,dig[i]个i-1位全排列的数
            cnt[j] += dp[i-1] * dig[i];
        LL temp = pow(10.0, i-1);
        for(int j=0; j<dig[i]; j++)//当前位0~dig[i],每个数有1e(i-1)种不同情况
            cnt[j] += temp;
            
        x -= temp * dig[i];//选最高位
        cnt[dig[i]] += (x + 1);//当前位为dig[i]时,一共有x+1种情况
        //if(i>1)
            cnt[0] -= pow(10.0, i-1);
    }
    
}

int main(){
    for(int i=1; i<N; i++)//计算i位数字全排列时,每一位数字的个数 
	    dp[i] = 10 * dp[i-1] + pow(10.0, i-1);
    cin >> a >> b;
    f(a-1, cnta);
    f(b, cntb);
    for(int i=0; i<10; i++){
        if(i) cout << " ";
        cout << cntb[i] - cnta[i];
    }
    cout << endl;
    return 0;
}

递归

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;

const int N = 14;

LL a, b, cnta[N], cntb[N], dp[N], pw[N];

void dfs2(LL x, int high, LL ans[N]){//统计1到x
    if(high==0) return;
    int u = x / pw[high-1] % 10;
    for(int i=0; i<u; i++) ans[i] += pw[high-1];
    for(int i=0; i<10; i++) ans[i] += dp[high-1] * u;
    x -= u * pw[high - 1];
    ans[u] += (x+1);
    ans[0] -= pw[high-1];
    dfs2(x, high-1, ans);
}

int dig(LL x){//统计数字x的位数
    int n = 0;
    do{
        n++;
        x /= 10;
    }while(x);
    return n;
}

int main(){
    pw[0] = 1;
    for(int i=1; i<N; i++) {//初始化
        pw[i] = pw[i-1] * 10;//pow(10, i)
        dp[i] = dp[i-1]*10 + pw[i-1];//第i位的每一个数字的个数
    }
    cin >> a >> b;
    dfs2(a-1, dig(a-1), cnta);
    dfs2(b, dig(b), cntb);
    for(int i=0; i<10; i++){
        if(i) cout << " ";
        cout << cntb[i] - cnta[i];
    } 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木灬U6770

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值