How many 0‘s?
Description
A Benedict monk No.16 writes down the decimal representations of all natural numbers between and including m and n, m ≤ n. How many 0‘s will he write down?
Input
Input consists of a sequence of lines. Each line contains two unsigned 32-bit integers m and n, m ≤ n. The last line of input has the value of m negative and this line should not be processed.
Output
For each line of input print one line of output with one integer number giving the number of 0‘s written down by the monk.
Sample Input
10 11 100 200 0 500 1234567890 2345678901 0 4294967295 -1 -1
Sample Output
1 22 92 987654304 3825876150
题意简单分析:给你两个数a和b,求从a手写到b,总共需要写多少个零(没有前置零)。先求出0到b需要的数num1,再求出0到a-1需要的数num2,用num1减去num2就是答案。
问题可以递归分解成子问题,一步步简化处理;例如9287,拆分成五段来求零出现的次数——0-999, 1000-8999,9000-9199, 9200-9279, 9280-9287;
chart数组,chart[i]表示从自然数0到i位(999..99)所写下的0 的个数(都是自然数不存在前置零)!上面的栗子中,第一段直接可以调用chart[]数组得出结果。(用chart[]省时,省步骤!第一次写没法推出chart数组,就暴力跑了一次;第二次,我觉得打表不是多稳,于是又花了两个小时坚持去推chart[]表,公式起始很简单,受cnt函数的影响,我起初一直仿照推下面cnt函数的方式去推——一直无果!后来,仔细跳出了推cnt的思路出来想想,起始就一行就够了!)
dp数组,dp[i]表示从i位的(0...0)写到i位的(9...9)需要的0的个数,相当于给你i位数求这i位数的可能的所有组合(含前置零);接着上面的栗子中,第二段中,1000-8999可以看做1xxx,2xxx,3xxx,...,8xxx(xxx表示三位数的所有可能的组合个数);第三段,9000-9199, 可以看做90xx,91xx (xxx表示两位数的所有可能的组合个数);依次类推。
fact数组,fact[i]数组存10的i 次幂= i位数的用0-9的全排列结果。
建立上述三个数组以后,从高位向低位按照乘法和加法原则进行迭代求和即可。
#include<stdio.h> #include<iostream> #include<cmath> #include<algorithm> #include<queue> #include<cstring> using namespace std; typedef long long ll; #define eps 1e-10 ll dp[11],fact[12]; ll chart[20]; ll arr[20]={0}; //由低位爬向高位 ll f3(ll a[],int i,int j){ //计算数组a[]第i到第j位构成的数字,如9086,f3(1,2)返回86 ll s=0; if(i<j)swap(i,j); for(;i>=j;i--){ s=s*10+a[i]; } return s; } void init() { fact[1]=10; //存10的次幂 for(int i=2;i<=10;i++) fact[i]=fact[i-1]*10; dp[1]=1;dp[2]=20; for(int i=3;i<=10;i++) dp[i]=fact[i-1]+10*dp[i-1]; //表示从i位的(0...0)写到i位的(99..9)需要的0的个数 chart[1]=1; for(int i=2;i<=9;i++) //表示从0到共i位的(99..99)需要写的0的个数 chart[i]=chart[i-1]+dp[i-1]*9; return ; } ll cnt(int pos,int len) //返回自然数0 到 n的含零数 { ll ans=0; if(pos==len&&len>1){ //pos为最高位 ans+=(arr[pos]-1)*dp[pos-1]+ chart[pos-1]+ cnt(pos-1,len); } else if(pos==len&&len==1){ //整个N只是个位数时 ans=1; } else if(pos!=1){ //当不是最高位和最后一位时 if(arr[pos]==0){ ans+= f3(arr,1,pos) +cnt(pos-1,len)+1; //1表示pos位0时 } else{ ans+= arr[pos]*dp[pos-1]+ cnt(pos-1,len)+fact[pos-1]; //fact[i-1]表示该为为0时 } } else{ //最后一位时 if(arr[pos]==0){ ans+=1; } else{ ans+=1; //最后一位的0 } } // printf("pos=%d ans=%d\n",pos,ans); return ans; } ll solve(ll n) //主要就是把n进行数位分离,然后调用cnt统计 { if(n<0)return (ll)0; if(n==0)return 1; ll ans=0,len; len=(ll)((int)log10(n*1.0)+1);//计算n的位数 int i=1; while(n>0) { arr[i++]=n%10;n/=10; } ans=cnt(len,len); return ans; } int main() { ll m,n; init(); while(scanf("%lld%lld",&m,&n)!=EOF) //题意:m<=n { if(n==-1&&m==-1)break; printf("%lld\n",solve(n)-solve(m-1)); } return 0; } //打表代码,大概等10秒钟可以跑完 /*void creat_chart(){ freopen("output.txt","w",stdout); ll ss=0;int k=1; for(ll i=0;i<=(ll)fact[9];i++){ ll j=i; if(j==0)ss++; while(j>0){ if(j%(ll)10==(ll)0)ss++; j/=(ll)10; } if(i==fact[k]-1){ printf("0到%lld的含0数为: %lld\n",i,ss);k++; } } printf("OK\n"); fclose(stdout); }*/ /*得到chart【】表格 结果如下: 0到9的含0数为: 1 0到99的含0数为: 10 0到999的含0数为: 190 0到9999的含0数为: 2890 0到99999的含0数为: 38890 0到999999的含0数为: 488890 0到9999999的含0数为: 5888890 0到99999999的含0数为: 68888890 0到999999999的含0数为: 788888890 OK*/