【BZOJ 1833】【ZJOI 2010】[数位DP]count 数字计数

5 篇文章 0 订阅
3 篇文章 0 订阅

题目描述

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

题目分析

膜膜膜PoPoQQQ大爷。
首先,我们可以把求[a,b]中间的数量改成[1,b]-[1,a-1]。
然后考虑怎么求[1,x]。
首先,对于位数小于x的数字因为每位数可以随便取,所以每一个数字出现次数是一样的(允许前导0)。我们不妨预处理 gi 表示位数为 i 时数字出现的次数,gi=gi1×10+10i1,因为对于数字的第 i 位有10种取法,相应的前i1位就会被重复计算10次,而新增的第 i 位每一种数码又会出现10i1次。然而累加时还需要考虑前导0,我们可以先枚举位数,对于0累加时,我们不考虑第 i 位为0:ans=gi1×9;其他的就是 ans=gi1×9+10i1
现在考虑位数和x相同时怎么做,我们还是从高到低枚举每一位数,不妨设当前数为 now ,枚举的是第 pos 位,那么增量就是 t=10pos1 。若 now+t<=x 时,因为最高位没有达到上限,因此前 pos1 位可以随便取,产生的贡献是当前已确定的后几位数中的数码乘以 10pos1 (出现次数),加上前 pos1 位数不计前导0的 gpos
然后就完了,唯一需要注意的是因为枚举每一位数时都没有计算上限值,所以答案求出来是 [1,x) 于是我们还是要计算 [1,r+1)[1,l) 的值。

代码

/**************************************************************
    Problem: 1833
    User: szpszp
    Language: C++
    Result: Accepted
    Time:4 ms
    Memory:1292 kb
****************************************************************/

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

#define MAXN
#define MAXM
#define INF 0x3f3f3f3f
typedef long long int LL;

template<class T>
void Read(T &x){
    x=0;char c=getchar();bool flag=0;
    while(c<'0'||'9'<c){if(c=='-')flag=1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    if(flag)x=-x;
}

LL g[20],powten[20];
int num[15];
int len;

void getnum(LL x){
    len=0;
    while(x)num[++len]=x%10,x/=10;
}

LL ans[10];
void Solve(LL x,LL flag){
    getnum(x);

    for(int i=1;i<len;++i){
        ans[0]+=g[i-1]*9*flag;//0不能放在末尾
        for(int j=1;j<10;++j)
            ans[j]+=(g[i-1]*9+powten[i-1])*flag;
    }

    LL now=powten[len-1],t=now;
    int pos=len-1;
    while(now<x){
        while(now+t<=x){
            LL tmp=now/t;
            while(tmp)ans[tmp%10]+=t*flag,tmp/=10;
            for(int i=0;i<10;++i)ans[i]+=g[pos]*flag;
            now+=t;
        }
        t/=10,--pos;
    }
}

void init(){
    memset(ans,0,sizeof(ans));

    g[0]=0,powten[0]=1;
    for(LL i=1;i<15;++i){
        powten[i]=powten[i-1]*10;
        g[i]=g[i-1]*10+powten[i-1];
    }
}

int main(){
    init();

    LL l,r;
    Read(l),Read(r);

    Solve(r+1,1);
    Solve(l,-1);

    for(int i=0;i<9;++i)
        printf("%lld ",ans[i]);
    printf("%lld\n",ans[9]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值