打卡第四天 数位DP初步

学个数位DP,差点怀疑人生!

首先是题目类型:

对于限制条件check(x),求[l,r]之间满足check(i)的个数

朴素算法自然是

for (int i = l;i <= r; i++) {
    res += check(i);
}
cout << res << endl;

自然,面对1e12这类离谱数据暴力是解决不了问题的(但时间不够时首选)

这是,数位DP便站了出来

既然[l,r]这个区间直接递推不好处理,那[1,x]呢?

如果这个结果满足ans = [1,r] - [1,l - 1],那直接用前缀和思想+递推是不是就能直接搞定?

但问题就是,递推什么?

首先,1和r唯一的区别就是长度了 ,可以以数字长度,也就是数位作为一个元素pos。

每一位能取到0~9十位数字,依次循环搜索的话会出现bug(比如[1,123],会循环999)问题

出在上一位,如果本位的上一位是r的对应位数字,那么这里只能选择[0,r的对应位]。

如  r = 3574

在35??时,第三位只能选择0~7

但34??时,第三位能选择0~9

在347?时,虽然第四位的上一位是r对应位的数,但第二位不是,所以能取0~9。

所以我们需要另外的一个布尔变量limit表示高位限制

如果限制前导零,则前几位是0的数需要特殊判断,如果直接枚举太浪费时间,可以引入一个新布尔变量st专门处理前导零问题

自此,数位dp三大基本元素已经处理完毕,剩余的就是专门处理check的变量

我们可以构造出模板了

long long dfs (int pos,bool st,bool limit) {
    if (!pos) return 1;

    int rex = limit ? num[pos] : 9;
    long long ans = 0;

    for (int i = 0; i <= rex; i++) {
        ans += dfs(pos - 1,st && (i == 0),limit && (i == rex));
    }

    return ans;
}

long long part (long long x) {
    len = 0;
    while (x) {
        num[++len] = x % 10;
        x /= 10;
    }

    return dfs(len,1,1);
}

接下来就是例题了

洛谷P2602 [ZJOI2010] 数字计数

我们可以分别对0~9十位数字求出现次数,并用part(r) - part(l - 1),

很明显,这个数字需要用专门的变量记录,暂定为x好了。

不过对于222来说,我们需要他返还3,表示这里有三个3,单纯的到了就返回1显然不行,所以需要另一个变量sum记录发现的所需数字个数。



#include <iostream>
#include <bits/stdc++.h>

using namespace std;

long long l,r;
int len;
int num[15];
long long dp[15][15];//dp标配,记忆化搜索

long long dfs (int pos,int sum,int x,bool st,bool limit) {//pos第几位,sum有几个,x表示找谁,st标记前导零,limit标记高位限制
    if (!pos) return sum;//边界
    if (!limit && !st && dp[pos][sum] != -1) return dp[pos][sum];//limit和st是限制,-1在之前memset时初始化过

    int rex = limit ? num[pos] : 9;//本位可取最大值
    long long ans = 0;//结果

    for (int i = 0; i <= rex; i++) {
        ans += dfs(pos - 1,sum + ((!st || i) && (i == x)),x,st && (i == 0),limit && (i == rex));//自行理解吧,实在不行用if也行
    }

    if (!limit && !st) dp[pos][sum] = ans;
    return ans;
}

long long part (long long x/*[1,x]*/,int i/*找谁*/) {
    len = 0;
    while (x) {
        num[++len] = x % 10;
        x /= 10;
    }//预处理分解原数,好直接锁定第x位的数字
    memset(dp,-1,sizeof(dp));//初始化

    return dfs(len,0,i,1,1);
}

int main () {

    cin >> l >> r;

    for (int i = 0; i <= 9; i++) {
        cout << part(r,i) - part(l - 1,i) << " ";
    }
    cout << endl;

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值