BZOJ 1833 浅谈简单数位动态规划

本文探讨了数位动态规划的概念,重点在于如何处理数字范围内的数位计数问题。通过将问题转化为前缀和之差,我们可以预处理固定位数数字的出现次数,并用递推公式计算额外部分的影响。在处理过程中,需要特别注意0的情况,以及不允许前导0的限制。文章附带了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述
世界真的很大
所谓数位DP大概就是针对数字的一类操作
其特征相当之明显就不多说,一般都是问范围内什么数字出现多少次一类
而我们此时就通过直接对范围的数为进行递推或递归
本质上来说,其实更接近于递推或者记忆化搜索吧
看题先:
description

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

input

输入文件中仅包含一行两个整数a、b,含义如上所述。

output

输出文件中包含一行10个整数,分别表示0-9[a,b]中出现了多少次。

首先对于范围的问题我们一般都是拆成两个前缀和之差
所以问题就转化成了0到n中每个数码出现了多少次
首先考虑,假设n是一个m位数,那么m-1位肯定是可以取完而不受范围影响的,这也是数位DP很重要的一个性质
不考虑0的情况下,在所有m位数里,1到9出现的次数都应该是一样的,比如在所有的两位数,(允许前导0),1到9都各出现20次
而这个次数是可以预处理的
设f(i)表示i位数的次数,那么f(i)=10*f(i-1)+10^(i-1),还是比较好理解,大概就是i-1位数里面有多少次,增加了一位,就有10倍那么多,在加上新增加的这一位,有10^(i-1)这么多
只是0有点特别,需要递推处理
那么就可以很快得到m-1位数各个出现的次数,只需要考虑n多出的部分就行了。
而对于多出的部分的每一个数位i,都可以由f(i-1)*n第i位的数字得到,这也是由于只要这一位不等于n这一位的数字,那对于后面的数就无限制,递推的加上去就好
只是需要注意第m位是不允许有前导0的,最后要减去多的部分
完整代码:

#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long dnt;

dnt a,b;
dnt f[20],ten[20],cnt[2][20];

void DP(dnt n,int K)
{
    if(n==0) return ;
    int m=0,num[15];
    dnt res=n;
    memset(num,0,sizeof(num));
    while(n)
    {
        num[++m]=n%10;
        n/=10;
    }
    for(int i=1;i<m;i++) cnt[K][0]+=f[i-1]*9;
    for(int i=1;i<10;i++) cnt[K][i]=f[m-1];
    for(int i=m;i;i--)
    {
        res-=num[i]*ten[i-1];
        for(int j=0;j<num[i];j++) cnt[K][j]+=ten[i-1];
        for(int j=0;j<10;j++) cnt[K][j]+=f[i-1]*num[i];
        cnt[K][num[i]]+=res+1;
    }
    for(int i=0;i<10;i++) cnt[K][i]-=f[m-1];
    cnt[K][0]-=ten[m-1];
}
int main()
{
    ten[0]=1;
    for(int i=1;i<=15;i++)
    {
        ten[i]=ten[i-1]*10;
        f[i]=f[i-1]*10+ten[i-1];
    }
    scanf("%lld%lld",&a,&b);
    DP(a-1,0);
    DP(b,1);
    for(int i=0;i<9;i++)
        printf("%lld ",cnt[1][i]-cnt[0][i]);
    printf("%lld",cnt[1][9]-cnt[0][9]);
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/

嗯,就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值