快速求解1~n的每個數字出現的次數.
對於快速求解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]=
在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]);