BZOJ 1833: [ZJOI2010]count 数字计数 【数位DP】


1833: [ZJOI2010]count 数字计数

Time Limit: 3 Sec  Memory Limit: 64 MB

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。


【题目链接】 link


【题意】

RT


【思路】

用dp[i][j][k]表示长度为i,最高位为j的所有数中数码k出现的次数。

为了预处理出dp数组,我们现需要找到转移方程

  1. f[i][j][k]=∑f[i-1][l][k]              (j!=k)

  2. f[i][j][k]=∑f[i-1][l][k]+10i-1    (j!=k)

第一种情况就是直接在前面加一个非k的数,结果跟长度为i-1时相同

第二种情况就是在前面加一个k,因为后面i-1位共有2i-1个数,那么就相当于多了2i-1个k

然后就是对读入的数分别求出每个数码有几个,相减即可。


#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)

typedef long long ll;
const int maxn = 15;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-6;

ll fac[15];
ll dp[15][10][10];
int digit[15];
ll ans1[10],ans2[10];

void init()
{
    mst(dp,0);
    fac[0]=1;
    for(int i=1;i<=14;i++) fac[i]=fac[i-1]*10;
    for(int i=0;i<=9;i++) dp[1][i][i]=1;
    for(int i=2;i<=13;i++)
    for(int j=0;j<=9;j++)
    {
        for(int l=0;l<=9;l++)
        {
            for(int k=0;k<=9;k++)
            {
                dp[i][j][k]+=dp[i-1][l][k];
            }
        }
        dp[i][j][j]+=fac[i-1];
    }
}

void solve(ll x,ll *num)
{
    mst(digit,0);
    if(x==0) return;
    int len=0;
    ll tmp=x;
    while(tmp)
    {
        digit[++len]=tmp%10;
        tmp/=10;
    }
    for(int i=1;i<len;i++)  //长度小于len的所有情况
    for(int j=1;j<=9;j++)
    for(int k=0;k<=9;k++)
    {
        num[k]+=dp[i][j][k];
    }
    for(int i=1;i<=digit[len]-1;i++)  //长度等于len且最高位不超过digit[len]的所有情况
    for(int k=0;k<=9;k++)
    {
        num[k]+=dp[len][i][k];
    }
    x%=fac[len-1];
    num[digit[len]]+=x+1;            //例如x=4532,把x变为532,统计532,数码4的个数加上532+1(4000~4532)
    for(int i=len-1;i>0;i--)         //以此类推
    {
        for(int j=0;j<digit[i];j++)
        for(int k=0;k<=9;k++)
        {
            num[k]+=dp[i][j][k];
        }
        x%=fac[i-1];
        num[digit[i]]+=(x+1);
    }
}

int main()
{
    init();
    ll x,y;
    scanf("%lld%lld",&x,&y);
    solve(y,ans1);
    solve(x-1,ans2);
    for(int i=0;i<=8;i++) printf("%lld ",ans1[i]-ans2[i]);
    printf("%lld\n",ans1[9]-ans2[9]);
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值