[bzoj1833][DP]count 数字计数

123 篇文章 1 订阅
109 篇文章 4 订阅

Description

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

Input

输入文件中仅包含一行两个整数a、b,含义如上所述。

Output

输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

Sample Input

1 99

Sample Output

9 20 20 20 20 20 20 20 20 20

HINT

30%的数据中,a<=b<=10^6; 100%的数据中,a<=b<=10^12。

题解

真的是痛苦。。这题折磨了我三天
一开始想的是二维预处理+区间缩小。结果我发现二维的在缩小的情况下是无法转移的。。怎么办?一分钟后我发现可以换成三维
设num[i][j][k]为 位数为i的数字,以j开头中第k种数字有多少个
先想如何预处理吧。首先,每个num[i][j][j]肯定要赋值为10^(i-1)对吧。因为i-1位到i位中,肯定至少有10^(i-1)个数字是以j开头的。
预处理完后我们枚举i-1位。因为i位数字实际上就是i-1位这些数字+任意一个数字组合而来的。譬如34567这个五位数 实际上就是3+4567这个四位数组成的嘛。那么i-1位的结果肯定对于i是有用的。枚举i位数开头数字j,i-1位数开头数字k,继承状态l
num[i][j][l]+=num[i-1][k][l]
好了我们预处理完了。那么询问的区间很明显是可以用大区间减去小区间对吧。
做一遍0~r再做一遍0~l-1最后输出就行了
怎么计算?首先对于位数<边界数位数 的数字,我们直接加就好了
譬如23005,我们把1~4位数的结果当然全部都要先加起来嘛。然后再处理其他的
对于位数等于边界数位数的数。我们就要一点一点加了
还是上面这个23005的例子。我们已经预处理过num[5][1][k]了对吧。而且10000~19999这些数一定可以被加起来。那么就直接加咯
那20000~23005怎么办??观察一下。可以拆成四位数的方法0~3005对不对?只要加上以2开头的五位数的个数就好了啊
那么这样一层一层叠下去。。就ok了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
LL num[15][15][15];//i位 最高位为j 第k个数字出现次数 
int s[15];
int Get(LL u)
{
    int ret=0;
    while(u)
    {
        s[++ret]=u%10;
        u/=10;
    }
    return ret;
}
LL pow(int u)
{
    LL ret=1,a=10;
    while(u!=0)
    {
        if(u%2==1)ret=ret*a;
        a=a*a;u/=2;
    }
    return ret;
}
LL ans[15][2];
void sol(LL u,int op)
{
    if(u<0)return ;
    int len=Get(u);LL tmp=u;
    if(u!=0)ans[0][op]++;
    for(int i=1;i<len;i++)
        for(int j=1;j<=9;j++)
            for(int k=0;k<=9;k++)ans[k][op]+=num[i][j][k];
    for(int i=1;i<s[len];i++)
        for(int k=0;k<=9;k++)ans[k][op]+=num[len][i][k];
    ans[s[len]][op]+=tmp-(s[len]*pow(len-1))+1;
    tmp-=(s[len]*pow(len-1));
    for(int i=len-1;i>=1;i--)
    {
        for(int j=0;j<s[i];j++)
            for(int k=0;k<=9;k++)ans[k][op]+=num[i][j][k];
        ans[s[i]][op]+=tmp-(s[i]*pow(i-1))+1;
        tmp-=(s[i]*pow(i-1));
    }
}
int main()
{
    LL a,b;
    scanf("%lld%lld",&a,&b);
    int lenx=Get(a),leny=Get(b);
    for(int i=1;i<=12;i++)
        for(int j=0;j<=9;j++)
        {
            num[i][j][j]=pow(i-1);
            for(int k=0;k<=9;k++)
                for(int l=0;l<=9;l++)
                    num[i][j][k]+=num[i-1][l][k];
        }
    sol(b,0);
    sol(a-1,1);
    for(int i=0;i<9;i++)printf("%lld ",ans[i][0]-ans[i][1]);
    printf("%lld\n",ans[9][0]-ans[9][1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值