[BZOJ] 1833: [ZJOI2010]count 数字计数

1833: [ZJOI2010]count 数字计数

Time Limit: 3 Sec  Memory Limit: 64 MB
Submit: 3784  Solved: 1663
[Submit][Status][Discuss]

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。

Source

Analysis

数位动规模板题

蒟蒻的数位动规之旅可谓艰辛qwq

四处扒题解搞模板

最后还是用的自己的诡异表示AC

= =

首先状态表示 DP[ len ] 表示长度为 len 的,额,数码串所有状态包含的单个数码的数量

你看,如果不计算前导零的话,其实每个数码(0,1,2,3...)都是一样的

十个数码的数量都是重复数据

所以将前导零暂且扔掉,只储存指定长度的数码数量

那么引入一个 Pow[ len ] ,你看,如果给一个指定的数码串再多加一位,那么多加的那一位会使数量增益个,,,额

原来的数码串是形如 4357594 这样的 7 位长的

那么我们增加第 8 位时,原来的排列并不会改变,但是会增加 0435... 1435... 2435... 等

这样,填进第 8 位的数码就有 107 的增加量

那么定义 Pow[ i ] = 10i-1

就得到了 DP[ i ] = DP[ i-1 ] * 10 + Pow[ i ]

以下为对代码的解释

那么我们考虑这个数位动规的实质,它的实质就是对一棵类似字典树的东西计数,深度即长度

那么用上面那种极其简单的DP表示,我们可以解决掉一棵完整的子树,却不能很好的处理形如 85 这种不能单纯用长度来计数的数字

那么对于这种不完整子树,我们只要一层层往下处理就行了

如上图,我们可以用 DP 直接统计掉第二层的 0 - 7 以及第三层的 0 - 4

但是处于边界的 8 和 5 ... = =

我们这样考虑

用 DP 能够处理掉 8 左边的部分,那么 8 本身的答案对应到实际是这样的:

80 81 82 83 84 85

也就是,8 这棵树有多少叶子结点,他就有多少增益

那么拿 85 减去前面计数过的叶子结点,就是 8 的增益了

之后是前导零

前导零在树中是这样子的:

反而前导零比较容易处理

每一层都给 0 的计数减去一个 Pow[ len ] 即可

注意只有一位数的 0 是算数的,所以需要提前给 0 增加

Code

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 
 5 long long Pow[20],DP[20],a,b;
 6 
 7 struct data{
 8     long long d[20];
 9     data(){ for(int i = 0;i < 20;i++) d[i] = 0;}
10 };
11 
12 long long getlen(long long &x){
13     long long ret = 0; for(long long i = x;i;i /= 10) ret++;
14     return ret;
15 }
16 
17 data conc(long long x){
18     x++;
19     long long dig[20] = {0},lenx = 0;
20     data ans;
21     if(!x) return ans;
22     
23     for(long long i = x;i;i /= 10)
24         dig[lenx++] = i%10;
25     
26     ans.d[0]++;
27     for(long long p = lenx-1;p >= 0;p--){
28         ans.d[0] -= Pow[p+1];
29         for(long long i = 0;i < dig[p];i++){
30             ans.d[i] += Pow[p+1];
31             for(long long j = 0;j <= 9;j++){
32                 ans.d[j] += DP[p];
33             }
34         }x -= Pow[p+1]*dig[p];
35         ans.d[dig[p]] += x;
36     }
37     return ans;
38 }
39 
40 int main(){
41     Pow[0] = Pow[1] = 1;
42     for(long long i = 2;i <= 15;i++) Pow[i] = Pow[i-1]*10;
43     
44     scanf("%lld%lld",&a,&b);
45     long long lena = getlen(a),lenb = getlen(b);
46     
47     DP[1] = 1;
48     for(long long i = 2;i <= lenb+1;i++) DP[i] = DP[i-1]*10+Pow[i];
49     
50     data ret1 = conc(a-1);
51     data ret = conc(b);
52     
53     for(long long i = 0;i <= 9;i++) 
54         if(!i) printf("%lld",ret.d[i]-ret1.d[i]);
55         else printf(" %lld",ret.d[i]-ret1.d[i]);
56     
57     return 0;
58 }
qwq

 

转载于:https://www.cnblogs.com/Chorolop/p/7699091.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值