BZOJ1833 [ZJOI2010] count
【问题描述】
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
【输入格式】
输入文件中仅包含一行两个整数a、b,含义如上所述。
【输出格式】
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
【输入样例】
1 99
【输出样例】
9 20 20 20 20 20 20 20 20 20
【数据范围】
30%的数据中,a<=b<=10^6;
100%的数据中,a<=b<=10^12。
正解:数位DP
解题报告:
只要想清楚就很简单,首先问题肯定是把区间1-b和区间1-(a-1)出现的数码统计出来,相减输出来;暴力一个一个数的统计肯定是会超时的,我们先不管前导0,然后我们把数字分为n位,前n位出现的所有数码次数可以看出全是一样的,f[i]表示前i位出现的数码次数,f[i]=f[i-1]*10+10^(i-1);接下来,如果仍然允许前导0,但将数的上限考虑进来的话,我们只需要从高位向低位计算。设当前为第i位,数字的这一位的数字为k,那么对于那些这一位数字小于 k的数,他们的前i-1位是没有数的大小上限的,每个数字累加k*f(i-1)即可,同时0~k-1的数累加10^(i-1);而对于那些这一位的数字恰 好为k的数,为了不超过上限,应累加前i-1位的数字形成的数+1(全为0); 最后,我们来想一下如何处理不允许前导0的情况,其实我们可以一位一位算,因为前n-1位没有上限,第i位可以直接通过f[i-1]*9算出,再把1-9加上10^(i-1),这样即可保证没算包括前导0的情况。
1 #include <iostream> 2 #include <iomanip> 3 #include <cstdlib> 4 #include <cstdio> 5 #include <cmath> 6 #include <algorithm> 7 #include <string> 8 #include <cstring> 9 #define RG register 10 #define ll long long 11 12 using namespace std; 13 14 ll f[20],l[10],r[10]; 15 16 void solve(ll *x,ll s){ 17 if (!s) return; 18 ll k=s,m;RG int w=0,i,j,sum[15]; 19 while(k) sum[++w]=k%10,k/=10; 20 for (m=1,i=1; i<w; i++){ 21 x[0]+=f[i-1]*9; 22 for (j=1; j<=9; j++) x[j]+=f[i-1]*9+m; 23 m*=10; 24 } 25 k=s-sum[w]*m; 26 for (i=1; i<sum[w]; i++) x[i]+=m; 27 for (i=0; i<=9; i++) x[i]+=f[w-1]*(sum[w]-1); 28 x[sum[w]]+=k+1; 29 for (i=w-1; i; i--){ 30 m/=10,k-=sum[i]*m; 31 for (j=0; j<sum[i]; j++) x[j]+=m; 32 for (j=0; j<=9; j++) x[j]+=f[i-1]*sum[i]; 33 x[sum[i]]+=k+1; 34 } 35 return; 36 } 37 38 int main(){ 39 freopen("input.txt","r",stdin); 40 freopen("output.txt","w",stdout); 41 ll a,b,s;RG int i; 42 f[1]=1;s=10; 43 for (i=2; i<=15; i++) f[i]=f[i-1]*10+s,s*=10; 44 scanf("%lld%lld",&a,&b); 45 for (i=0; i<10; i++) l[i]=r[i]=0; 46 solve(l,a-1),solve(r,b); 47 printf("%lld",r[0]-l[0]); 48 for (i=1; i<=9; i++) printf(" %lld",r[i]-l[i]); 49 printf("\n"); 50 return 0; 51 }