数字0-9的数量 (数位dp ,技巧)

给出一段区间a-b,统计这个区间内0-9出现的次数。

比如 10-19,1出现11次(10,11,12,13,14,15,16,17,18,19,其中11包括2个1),其余数字各出现1次。

 收起

输入

两个数a,b(1 <= a <= b <= 10^18)

输出

输出共10行,分别是0-9出现的次数

输入样例

10 19

输出样例

1
11
1
1
1
1
1
1
1
1

思路:

统计[a,b]中 [0,9]出现的次数。

    eg: 3456 = 3000 + 400 +50 + 6
    因此我们可以算出来(1,3000)每一位出现的次数 等等...
    经过我好长好长时间的推算:
        1~100:  11 21,20,20,20,...,20
        1~200:  31 40,41,40,40,...,40    除 0 和 最高位 外其他都是二倍的关系

        1~1000: ?(先不处理0位)301,300,300,...,300

        1~10000: ?             4001,4000,4000,...,4000

    下面推导先排除0位的情况!!!

        由此我们可以知道,对于任意一个(百,千,万...)的数,由 1 开头的数
            如   100..0 (x个0) 得到的每位数都为 x * 10^(x-1), 1这个位数要 +1
            那么 y00..0(x个0)得到的每位数是上面各位数都 乘以 y ,y这个位数要 +1
        因此我们可以很快的推出 3000,400,50,... 的结果

        然后我们来考虑:
            3456 = 3000 + 456
        此时3又出现了456次,因此我们要加上456

    根据上述,我们对每一位都这样处理,累加结果即可。

    最后我们在来考虑前导零!!!

        首先我们可以按照上式(把忽略前导零,就是把它和其他数一样处理1)求出结果,
        最后,我们减去前导零的个数

代码如下:

/*
    统计[a,b]中 [0,9]出现的次数。

    eg: 3456 = 3000 + 400 +50 + 6
    因此我们可以算出来(1,3000)每一位出现的次数 等等...
    经过我好长好长时间的推算:
        1~100:  11 21,20,20,20,...,20
        1~200:  31 40,41,40,40,...,40    除 0 和 最高位 外其他都是二倍的关系

        1~1000: ?(先不处理0位)301,300,300,...,300

        1~10000: ?             4001,4000,4000,...,4000

    下面推导先排除0位的情况!!!

        由此我们可以知道,对于任意一个(百,千,万...)的数,由 1 开头的数
            如   100..0 (x个0) 得到的每位数都为 x * 10^(x-1), 1这个位数要 +1
            那么 y00..0(x个0)得到的每位数是上面各位数都 乘以 y ,y这个位数要 +1
        因此我们可以很快的推出 3000,400,50,... 的结果

        然后我们来考虑:
            3456 = 3000 + 456
        此时3又出现了456次,因此我们要加上456

    根据上述,我们对每一位都这样处理,累加结果即可。

    最后我们在来考虑前导零!!!

        首先我们可以按照上式(把忽略前导零,就是把它和其他数一样处理1)求出结果,
        最后,我们减去前导零的个数
*/
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int check(LL x)
{
    int ans=0;
    while(x)
    {
        ans++;
        x/=10;
    }
    return ans;
}
void work(LL *A,LL n)//计算从 1 -> n 各位出现的次数
{
    LL m=1;
    int len=check(n);
    LL x=n/10,y=n%10;
    for(int i=2; i<=len; i++)//先从十位开始处理
    {
        int r=x%10;  //这一位是 r
        LL u=m*10;  //  哪一位(十,百,千)
        for(int j=0; j<r; j++) //r + 后面部分,[0,r)每个数都出现了u次,先不管前导零,都处理;
            A[j]+=u;
        u=(i-1)*m*r; //这是每个数要加多少
        for(int j=0; j<10; j++)//都加上这个数
            A[j]+=u;

        A[r]+=y+1; //同时加上 大于r*u那部分
        m*=10;
        x/=10;
        y=r*m+y;
    }
    for(int i=1; i<=n%10; i++)//处理个位
        A[i]++;
    m=1;
    for(int i=1;i<len;i++)//把前到零的情况减去
    {
        m=m*10;
        A[0]-=m;
    }
}
int main()
{
    LL a,b;
    scanf("%lld%lld",&a,&b);
    LL A[15]={0},B[15]={0};
    if(a>b)swap(a,b);
    work(A,b);
    work(B,a-1);
    for(int i=0;i<10;i++)
        printf("%lld\n",A[i]-B[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值