P2602

· · 个人记录

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

a<=b<=10^12

Solution 1:

对每一位数我们直接把数拆分,然后直接考虑因为每个数出现概率均等,所以直接可以算出出现次数

以22258为例,() 22258=20000+2000+200+50+8

在不考虑首位时,

其中20000中(0000-9999)0,1,2 , 3....9,都出现了4000*2次

(例如0~99中共100(10x10)个数, 每个数都看作两位数(包含前导零),共100x2个单独数字出现)

2000,200,50,8以此类推

考虑首位时,考虑小于首位最大数的数字个数需要加上10000,

首位最大数个数增加2258

此时1~9已经计算出来,但0会因为前导零出现错误

最后我们减去前导零个数即可

如何计算前导零个数?

我们一100为例

前导零个数为 203-19-902-1*3

或者考虑直接算出0的个数

因为已经计算出1~9出现次数,此时我们用

19+902+1*3-Sum(1~9)就是0的个数

Solution 2:

我们直接考虑记忆化按位搜索

每次判断前面有没有前导零,这一位有没有越界(前面都是最大位时)

最后return 数字个数,从0~9按次DFS即可

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const int N=15;

ll f[N][2][N][2];

int num[N];

ll dfs(int len,bool im,int sum,bool zero,int s){
    ll ret=0;
    if(len==0) return sum;
    if(f[len][im][sum][zero]!=-1) return f[len][im][sum][zero];
    for(int i=0;i<=9;i++){
        if( im && i>num[len]) break;
        ll ask;
        bool fg,fs;
        if(i==num[len] && im==1) fg=1;
        else fg=0;
        ask=sum+( (zero!=1 || i!=0) && i==s);
        if(zero==1&&i==0) fs=1;
        else fs=0;
        ret+=dfs(len-1,fg,ask,fs,s);
    }
    f[len][im][sum][zero]=ret;
    return ret;
}

ll kp(ll x,int s){
    int len=0;
    while(x){
        num[++len]=x%10;
        x/=10;
    }
    memset(f,-1,sizeof(f));
    return dfs(len,1,0,1,s);
}

int main(){
    ll a,b;
    scanf("%lld%lld",&a,&b);
    for(int i=0;i<=9;i++){
        printf("%lld ",kp(b,i)-kp(a-1,i));
    }
    return 0;
}