SP3928 MDIGITS - Counting Digits 题解

SP3928 MDIGITS - Counting Digits 题解

题目链接:SP3928 MDIGITS - Counting Digits

题意

给定两个整数 a a a b b b,求 a a a b b b 之间的所有数字中 0 0 0 ~ 9 9 9 出现次数。

例如, a a a = 1024 1024 1024 b b b = 1032 1032 1032,则 a a a b b b 之间共有 9 9 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 10 10 次,1 出现 10 10 10 次,2 出现 7 7 7 次,3 出现 3 3 3 次等等……

数位dp的基础题

f i f_i fi 表示满 i i i 位数字(包括前导零),每种数字的出现次数
f 1 = 1 f i = f i − 1 × 10 + 1 0 i − 1 f_1 = 1 \\f_i = f_{i-1} \times 10 + 10^{i-1} f1=1fi=fi1×10+10i1
其中 f i − 1 × 10 f_{i-1}\times 10 fi1×10 i − 1 i-1 i1 位及以下位的贡献

例如 7000 ∼ 7999 ,   8000 ∼ 8999 \tt{7000\sim7999,~8000\sim8999} 70007999, 80008999

显然 000 ∼ 999 \tt{000\sim999} 000999出现了 10 10 10

1 0 i − 1 10^{i-1} 10i1 是第 i i i 位的贡献,比如 9000 ∼ 9999 \tt{9000 \sim 9999} 90009999 ,第 4 4 4 位的 9 \tt{9} 9 出现了 1 0 3 10^3 103

然后我们再考虑怎么获得答案

首先 [ l , r ] [l,r] [l,r] 可以拆分为 [ 0 , l − 1 ] ,   [ 0 , r ] [0,l-1],~[0,r] [0,l1], [0,r] 两个询问(基本的容斥)

然后考虑一个数 A B C ‾ \tt{\overline{ABC}} ABC ,不难发现, 0 \tt{0} 0 A 00 ‾ \tt{\overline{A00}} A00 每个非最高位数都出现了 A \tt{A} A × f 2 \times f_2 ×f2

而最高位 0 ∼ A − 1 \tt{0\sim A-1} 0A1 都各出现了 1 0 2 10^2 102

注:这里 0 \tt 0 0 是前导零,所以其实不会算进去,这里只是为了方便分析

那么 A \tt{A} A 呢?不难发现它出现了 B C ‾ + 1 \tt{\overline{BC}+1} BC+1

对于 B \tt{B} B ,同样的处理方式。

怎么一股机翻的味道

然后我们就搞定这道题了

时间复杂度 O ( Q l b ) O(Qlb) O(Qlb)

其中 l l l 表示最大位数, b b b 表示进制,这题里为 10 10 10

代码:(非dfs写法)

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)()

int a,b,mi[25],cnt1[25],cnt2[25],num[25],f[25];
void clear()
{
    memset(cnt1,0,sizeof(cnt1));
    memset(cnt2,0,sizeof(cnt2));
}
void solve(int x,int *cnt)
{
    int len=0;
    memset(num,0,sizeof(num));
    while(x)
    {
        num[++len]=x%10;
        x/=10;
    }
    for(int i=len; i>=1; i--)
    {
        for(int j=0; j<=9; j++)
            cnt[j]+=f[i-1]*num[i];
        for(int j=0; j<num[i]; j++)
            cnt[j]+=mi[i-1];
        int res=0;
        for(int j=i-1; j>=1; j--)
        {
            res = res * 10 + num[j];
        }
        cnt[num[i]]+=res+1;
        cnt[0]-=mi[i-1];
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    mi[0]=1;
    for(int i=1; i<=18; i++)
    {
        f[i]=f[i-1]*10+mi[i-1];
        mi[i]=10*mi[i-1];
    }
    while(cin >> a >> b)
    {
        if(!a&&!b)return 0;
        if(a>b)swap(a,b);
        clear();
        solve(a-1,cnt1);solve(b,cnt2);
        for(int i=0; i<=9; i++)
            cout << cnt2[i]-cnt1[i] << " \n"[i==9];
    }
    return 0;
}

转载请说明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值