[ZJOI2010] 数字统计
[ZJOI2010] 数字统计
题目
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
INPUT
输入文件中仅包含一行两个整数a、b,含义如上所述
OUTPUT
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
SAMPLE
INPUT
1 99
OUTPUT
9 20 20 20 20 20 20 20 20 20
解题报告
第二道数位$dp$,找着点感觉了?
首先,我们预处理出来从低向高位数第$i$位数,每个数码出现的次数,递推式很简单
$$f[i]=10*f[i-1]+10^{i-1}$$
我们分两部分考虑即可,第$i$位为该数字的数有$10^{i-1}$个,后$i-1$位数该该数字出现的次数为$f[i-1]$,前面的数共有$10$种可能(允许前导0),故$f[i]=10*f[i-1]+10^{i-1}$
然后考虑如何统计答案。
对于低于该数位数的数,我们可以去除前导零,对上式进行变形(具体式子见代码),求和即可
对于位数等于该数位数的数,我们可以逐位枚举统计,利用处理出来的$f$数组和$10^{i}$进行转移即可
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 typedef long long L; 6 L a,b,f[20],pw[20],ans[20]; 7 int num[40],top; 8 inline void solve(L x,int flag){ 9 if(!x)return; 10 top=0;L ret(x); 11 while(x){num[++top]=x%10;x/=10;} 12 for(int i=1;i<top;++i) 13 for(int j=0;j<=9;++j) 14 ans[j]+=flag*(9*f[i-1]+(j?pw[i-1]:0)); 15 for(int i=top;i;--i){ 16 ret-=num[i]*pw[i-1];ans[num[i]]+=(ret+1)*flag; 17 for(int j=(i==top);j<num[i];++j)ans[j]+=pw[i-1]*flag; 18 for(int j=0;j<=9;++j)ans[j]+=f[i-1]*(num[i]-(i==top))*flag; 19 } 20 } 21 int main(){ 22 freopen("countzj.in","r",stdin);freopen("countzj.out","w",stdout); 23 scanf("%lld%lld",&a,&b); 24 pw[0]=1;for(int i=1;i<=13;++i)f[i]=10*f[i-1]+pw[i-1],pw[i]=pw[i-1]*10; 25 solve(b,1);solve(a-1,-1); 26 printf("%lld",ans[0]);for(int i=1;i<=9;++i)printf(" %lld",ans[i]); 27 }