P2602 [ZJOI2010] 数字计数

题意:

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

数据范围:1 ≤ a ≤ b ≤ 10^{12}


题解:

这道题的范围到10^{12},O(n)的做法铁超时,这里标答是数位DP记搜


1.预处理

打表看一下,0-9出现的次数有规律,对于0-9 ,0 - 99,0 - 999这样的数据1-9个数是相等的(0特殊)

我们令f[i]为最高位为i位时,1-9出现的次数,那么f[i]=i*10^{i-1},如果不考虑前导0,0与其他数字的次数是相等的,这里观察发现,0的出现次数=f[i]-10^{i-1}-10^{i-2}......10^{0}(这里去前导0我放到了递归里)

void init(){//预处理,把f[i][k]全求出来
    for(int i=1;i<=13;i++)//i到第13位
        for(int k=0;k<=9;k++)
            f[i][k]= i * pow(10,i-1);
}

2.

之后我们按位从高到低拆分,这里我们可以发现

对于3939,找3的次数,我们可以将其分成 三部分:最高位*f[i-1] + find(去掉最高位后的数(939)+ 最高位的出现的次数(939+1=940)

特别的,这里需要提出最高位maxn,与要找的数k进行讨论,maxn=k,则 ans[k] += 10^{i-1}

maxn == k(如样例),ans[k] += 下一位为最高位的数+1 (939 + 1),maxn < k忽略。

最后答案等于 sum[0,B] - sum[0,A-1](前缀和


3.完整代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[20][10],a,b,ansa[10],ansb[10],cnta,cntb;//f[i][k]表示第i位数字k有多少

void init(){//预处理,把f[i][k]全求出来
    for(int i=1;i<=13;i++)//i到第13位
        for(int k=0;k<=9;k++)
            f[i][k]= i * pow(10,i-1);
}

void finda(ll num,ll cnt,ll k){//数字num,cnt当前的位数
    if(cnt==0) return ;
    ll maxn = num / pow(10,cnt-1);//maxn是最高位的数

    ansa[k] += f[cnt-1][k] * maxn;
    if(maxn > k) ansa[k] += (ll)pow(10,cnt-1) ;
    else if(maxn == k)ansa[k] += num % (ll)pow(10,cnt-1) + 1;
    if(k == 0) ansa[k] -= pow(10 , cnt-1);
    finda(num % (ll)pow(10,cnt-1) , cnt-1 , k);
}
void findb(ll num,ll cnt,ll k){//数字num,cnt当前的位数
    if(cnt==0) return ;
    ll maxn = num / pow(10,cnt-1);//maxn是最高位的数
    
    ansb[k] += f[cnt-1][k] * maxn;
    if(maxn > k) ansb[k] += (ll)pow(10,cnt-1) ;
    else if(maxn == k)ansb[k] += num % (ll)pow(10,cnt-1) + 1;
    if(k == 0) ansb[k] -= pow(10 , cnt-1);
    findb(num % (ll)pow(10,cnt-1) , cnt-1 , k);
}
void getcnt(){
    ll aa = a, bb = b;
    while(aa){
        cnta++;
        aa/=10;
    }
    while(bb){
        cntb++;
        bb/=10;
    }
}
int main(){
    cin >> a >> b;
    a--;
    init();
    getcnt();
    for(int k = 0;k <= 9;k++)
        finda(a,cnta,k);
    for(int k = 0;k <= 9;k++)
        findb(b,cntb,k);
    for(int k = 0;k <= 9;k++)
        cout<<ansb[k] - ansa[k]<<" ";
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值