1. 程式人生 > >快速求解1~n的每個數字出現的次數.

快速求解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]=

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]);