[ZJOI2010]数字计数,洛谷P2606,数位Dp(注意前导零带来的影响)

正题

      这题我们看道题面,看上去很简单,但是如果暴力枚举的时间复杂度是很高的。

      所以我们就会想到用数位Dp来去求解。

      关于数位Dp,其实是一种处理数字局限性的计算方法。

      关键就在于怎么样让这个“局限性”体现出来的同时,加快我们程序运行的速度。

      那么对于这题,它要求解的目标是每个数码有多少个。

      我们会想到00~99处理出来的数字都是一样的,在此中0有10个,1有20个……

      所以对于2XX,和3XX,他们后面的数位是不用重复求解的,这是我们可以把它们存起来。

      定义solve[i]表示填到第i位到最后一位的状态总和(只计算000..0~999..9)是多少。

      当前这个位置的数码,也要进行存储,如果后面可以填000~999那么就是k*10^n(其中k为当前枚举的数字,10^n表示这一位到最后共有n位),否则如果后面的数有限制(比如说547,当前枚举到5这一位,后面只能填00~47),那么5就会被计算(47+1)=48次,所以还要预处理一个后缀和g。

      然后就完成了这一道大水题。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

long long x,y;
long long solve[100];//无限制且没有前导0时第i位到最后一位的状态总和
long long val[100];//表示第i位的值
long long ci[100];//表示10的i次方
long long g[100];/表示后缀

long long Dp(int pos,int tf,int k,int all){
    if(pos==0) return 0;//枚举完就返回,不产生价值
    if(!tf && !all && solve[pos]!=-1) return solve[pos];//如果已经处理过就返回已经处理过的值
    long long res=0;//否则就计算
    int end=tf?val[pos]:9;//枚举的上限根据前面的有没有“满”来决定
    for(int i=0;i<=end;i++)	{
        res+=Dp(pos-1,tf&&i==end,k,all&&i==0);//往下搜
        if(!(all && i==0) && i==k) res+=((i==end&&tf)?g[pos-1]+1:ci[pos-1]);//如果前面没有前导零而且i等于当前所求解的k数码,那么他就会
    }//前所求解的k数码,那么他就会带来相应的价值
    if(!all && !tf) solve[pos]=res;//记忆化
    return res;
}

long long gett(long long v,int k){
    memset(solve,-1,sizeof(solve));
    int len=0;
    while(v!=0){
        val[++len]=v%10;
        v/=10;
        g[len]=g[len-1]+val[len]*ci[len-1];//预处理后缀
    }
    return Dp(len,1,k,1);
}

int main(){
    ci[0]=1;
    for(int i=1;i<=15;i++) ci[i]=ci[i-1]*10;
    long long tot=0;
    scanf("%lld %lld",&x,&y);
    x--;
    for(int i=0;i<=9;i++)//根据每一个数码进行求解
        printf("%lld ",gett(y,i)-gett(x,i));
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值