概述
对于快速求解1~n中每个数字出现次数的问题:
在做奥数题时,我们很多时候都会遇到这类问题:在1~999页中数字"1"出现了几次,数字"2"出现了几次...
对于这类问题在用笔算时我们一般是把1~999分为几个阶段:
1~9
10~99
100~999
...
10^n~10^n*10-1
这样子一个阶段一个阶段的求解,可以有效的快速的解答出来,然而在编程中,遇到这类问题,我们一般是用暴力枚举的方法,把1~999的每个数字都用字符串,然后再在字符串里计算tot。但是这样子的效率不高,O(n)的算法,使得在很多时候我们不能体现出计算机的最强大的一方面,当然如果数据范围大一点,O(n)也是不行的,所以今天,我要讲解一下如何用最简单的方法求出最快速的答案。
我们先想一想在暴力枚举中,我们的思路是与奥数不同的,而我们是否可以把思路转向另一侧,用奥数的方法解答出来呢?
例子1:
求出1~999里“1”出现的次数。
这道题首先我们现向奥数的方法划为三个阶段,然后计算1在个位,在十位,在百位出现的次数。
很明显:
个位:
在100~999这个阶段里'1'在个位出现了[1..9],[0..9],[1]次,9*10=90次。
在10~99这个阶段里'1'则出现了[1..9],[1]次,9*1=9次。
在1~9这个阶段里'1'出现了1次。
十位:
在100~999这个阶段里'1'在十位出现了[1..9],[1],[0..9]次,9*10=90次。
在10~99这个阶段里'1'在十位出现了[1][0..9]次,1*10=10次。
百位:
在100~999这个阶段里'1'在百位共出现了[1][0..9][0..9],10*10=100次。
所以:在1~999个数当中,数字“1”出现了90+9+1+90+10+100=300次。
至此,我们已经能大概判断出有什么规律了,但是还不是非常准确,我们再看一个例子。
例子2:
求出1~684里“1”出现的次数。
同样划分阶段,再计算:
个位:
在100~684这个阶段里1在个位出现了[1..5],[0..9],[1]+[6],[0..8],[1]=5*10+1*9=59次。
在10~99这个阶段里1在个位出现了[1..9],[1]次,1*9=9次。
在1~9这个阶段里1在个位毋庸置疑出现了1次。
十位:
在100~684这个阶段里1在十位出现了[1..6],[1],[0..9]=6*10=60次。
在10~99这个阶段里1在十位同样出现了10次。
百位:
在100~684这个阶段里1在百位出现了[1],[0..9],[0..9]=10*10=100次。
所以:在1~684页中,数字“1”出现了59+9+1+60+10+100=239次。
从这里两个简单的例子我们可以发现:在计算个位在十、百位数的次数的时候,其实就等于这个数的前两位
计算1:
例如计算‘1’在个位出现次数:
999:(90)+(9)=99
684:(59)+(9)=68 (括号分别指在十、百为出现的次数)
所以,在计算个位的时候其实就等于这个数的个位前面位数的数。
然后我们还可以发现在十位中,也是十位前面位数的数*十位后面数的位:10^1.
然后百位的计算也是一样的,因为前面没数了,所以后面直接乘以一个10^2.
计算1所计算的是:以当前要求的数码k不变,其位置i前后数位所能为其组成的方案数。
计算2:
例如计算以‘1’为当前位置i的开头的方案数——什么意思呢?就是说例如当前
999,位置i=2——也就是以1开头的十位数,i=3——以1开头的百位数,i=4——以1开头的千位数,i=1——以1开头的个位数。
所以像上面的两个例子:
999,在算个位的时候,其实是有三步的,第三步就是以1开头方案数——只有1次
在算十位的时候,其实是有两步的,第二步就是以1开头的方案数——有10次
在算百位的时候,其实只有一步的,第一步就是以1开头的方案数——有100次
还有栗子:
184,计算个位的时候,以1开头的方案数——1次
计算十位的时候,以1开头的方案数——10次
计算百位的时候,以1开头的方案数——84次
由此我们可得出结论:当以数码k,在第i位的时候,只要数码k>s[length(s)-i+1],就可以直接加10^(i-1),否则,如果k=s[length(s)-i+1]的话,就加上其后面的数所组成的数。再如果——小于的话——则不用计算。
还是贴一下程序来理解吧:
function qiu(k:Longint):int64;//k代表的是要求的数码
var
i:Longint;
begin
qiu:=0;
for i:=1 to length(s) do
begin
inc(qiu,(n div a[i+1])*a[i]);//这里就是算i其前后位数的方案数。
if ord(s[length(s)-i+1])-48>k then inc(qiu,a[i]); //这里就是算如果以当前位为开头,要求的这一位是大于k的,则没有影响,直接+a[i]就行,这里就好似上面举的两个例子中计算个、十、百位的其中一步。
if ord(s[length(s)-i+1])-48=k then inc(qiu,n mod a[i]+1); //这里就是上面说的如果是大于k的,直接加a[i],但如果是=k的,则加后面数的大小,例如158,则加58.如果是小于k的则没必要计算了。
end;
end;
a[i]表示i^10,qiu记录总和,k表示要求的数字编号,s表示要求的数。
这个方法只是计算1~9的,如果说要计算0的话,则在结果后面还要减去一个11111...
for i:=1 to length(s) do
dec(ans,a[i]);
最后
以上就是谨慎绿草为你收集整理的快速求解1~n的每个数字出现的次数.对于快速求解1~n中每个数字出现次数的问题:例子1:的全部内容,希望文章能够帮你解决快速求解1~n的每个数字出现的次数.对于快速求解1~n中每个数字出现次数的问题:例子1:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复