【codevs1359】【BZOJ1833】数字计数,进击的学弟与数位DP

传送门1
传送门2
写在前面:有关数位DP的题目都会做很久吗……
思路:
(神犇学弟遇到题目的加强版,暴力变DP)
30分直接暴力枚举拆位
100分考虑类似前缀和的做法,我们只要得到[0,a]的各位数码出现次数,同理可得[0,b]的出现次数,作差即可,这里就需要用到数位DP
转移并不困难,f[i][j][k]表示最多是i位数,且最高位为j的所有数中k的出现次数,具体的范围表达可以看图
这里写图片描述
显然i=1~12,j,k=0~9。而且,f[i][j][k]是可以转移f[i][j+1][k](j<9)和f[i+1][0][k](j=9),注意这里的f[i+1][0][k]表示最高位为0,但实际上就是f[i][9][k]
转移完后我们就可以直接得到0到1..9,19,29..99,199,299..999,1999…….各位数码出现的次数了,但这显然不能满足我们的要求,所以我们可以分块计算,比如12345,我们可以得到0~9999的值,再得到0~1999(实际上是10000~11999,但首位的1恒定,所以我们可以直接计算得到),以此类推到0~299,0~39,0~5。
(感觉还是各种口胡说不清楚,如果有问题还是评论吧)
注意:
1.个位注意单独计算
2.我们并没有办法通过f[i][j][0]直接得到0出现的次数,但它就是总出现次数-1..9出现的次数,所以我们只要算出总出现次数就可以了
3.sum开的int,没有1A不开心

#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL a,b;
LL f[14][10][10],p[13];
struct node
{
    LL sum[10];
    void prin()
    {
        for (int i=0;i<=9;i++)
        {
            printf("%lld",sum[i]);
            if (i<9) putchar(' ');
        }
    }
    void clear(){memset(sum,0,sizeof(sum));}
};
node sub(node a,node b)
{
    node c;
    for(int i=0;i<=9;i++) c.sum[i]=a.sum[i]-b.sum[i];
    return c;
}
node solve(LL x)
{
    node y;
    y.clear();
    if (x<=0)
    {
        y.sum[0]=(x==0);
        return y;
    }
    for (int i=1;i<=9;i++)
    {
        LL t=x,k=0,wei=log10(x);
        while (t)
        {
            if (t/p[wei])
                y.sum[i]+=f[wei+1][t/p[wei]-1][i]+k*(t/p[wei]*p[wei]);
            k+=(i==t/p[wei]);t%=p[wei];wei--;
        }
        y.sum[i]+=k;
    }
    LL tot=0,wei=log10(x);
    while (wei>=0)
    {
        tot+=(wei+1)*(x-p[wei]+1+(x<10));
        x=p[wei]-1;
        wei--;
    }
    for (int i=1;i<=9;i++) tot-=y.sum[i];
    y.sum[0]=tot;
    return y;
}
main()
{
    scanf("%lld%lld",&a,&b);
    p[0]=1;
    for(int i=1;i<=12;i++) p[i]=p[i-1]*10; 
    f[1][0][0]=1;
    for (int i=1;i<=12;i++)
        for(int j=0;j<=9;j++)
            for (int k=0;k<=9;k++)
                if (j<9)
                    f[i][j+1][k]+=f[i][j][k]+f[i-1][9][k]+p[i-1]*(j+1==k);
                else
                    f[i+1][0][k]+=f[i][j][k];
    sub(solve(b),solve(a-1)).prin();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值