聊聊數論與數列的一個問題
一、背景
有人問我,如果給一個數列,子數列有多少種?
問:連續嗎?有序嗎?可以為空嗎? 答:非連續、有序、非空。
這個顯然是個組合問題。
假設數列長度為 n,則答案顯然有2^n - 1
種。
大概思路就是挑1個、挑2個至挑n個。
公式如下:
C(n, 1) + C(n, 2) + ... + C(n, n) = 2^n - 1
然後問題加強了。 如果子數列需要滿足序列的值能夠整除數列的下標,問序列有多少個?
於是這道題變得有意思了。
二、資料範圍
任何問題都要看資料範圍。
如果範圍比較小,即使是暴力的方法,也是可行的方法。 但是範圍大了,就需要找到一種有效的方法了。
比如,這道題的序列個數有2^n-1
個,n
稍微大一點,暴力的方法就不可接受了。
那具體資料範圍是多少呢?
回答:最多有10W
個數字,數字的值在100W
內。
看到10W
意味著這道題的複雜度必須小於O(n^2)
了。
也就是掃描一遍數列後就要求出答案了。
三、第一次分治
既然只能掃描一次就求到答案,那問題就需要拆分了。
原問題:問長度為n
的序列有多少個滿足條件的子序列?
第一次拆分:長度為n
的序列中,有多少個滿足條件的子序列是以第i
個數結尾的?
原問題我們記為F(n)
。
第一次拆分我們記為F1(i)
。
顯然可以看出,之間的關係下面:
F(n) = F1(1) + F1(2) + ... + F1(n)
如果我們能夠分別算出F1(i)
,那最終答案求和就可以計算出來了。
四、第二次拆分
對於F1(i)
,由於第i
個數是子序列的結尾,序列又是有序的。
所以第i
個數之後的數字對於F1(i)
完全無影響。
只有第i
個數之前的數字才會影響F1(i)
。
所以F1(n)
的定義我們可以調整為:
長度為n
的序列中,以最後一個數字為結尾的滿足條件的子序列有多少個?
滿足條件?滿足的是什麼條件呢? 答:子序列的下標能夠整除子序列對應下標的值。
也就是滿足條件的F1(n)
個子序列的長度,肯定能夠被value[n]
整除。
換言之,就是滿足條件的子序列的長度肯定是value[n]
的一個約數。
於是這裡就可以繼續對問題拆分了。
第二次拆分:以vaule[n]
結尾的子序列中,有多少個滿足條件的子序列長度為vaule[n]
的第j
個約數?
第二次拆分後,定義我們稱為F2(n, j)
。
假設value[n]
有k
個約數,那麼我們可以得到一個等式關係:
F1(n) = F2(n, 1) + F2(n, 2) + ... + F2(n, k)
也就是求出尾數所有約數為長度的滿足條件的個數,就求出了尾數滿足條件的個數。
五、統計
我們現在的問題是求指定尾數、指定子序列長度、滿足條件的個數。
問題具體化,尾數位置為i
,子序列長度為k
。
這樣我們就可以把問題轉化為:前i-1
個數字中,滿足條件的長度為k-1
的個數。
為什麼兩個是等價的呢?
證明如下:
對於前i-1
個數字中,任何滿足長度為k-1
且滿足條件的子序列,把第i
個數字追加到子序列最後,依舊滿足條件,且長度為k
。
而對於前i-1
個長度不為k-1
的任何子序列,第i
個數字追加上去後,長度不可能為k
。
證明結束。
使用公式就是如下:
F2(n, k) = stat(n-1, k-1)
而對於stat(n, k)
組成也很容易看出來,一部分包含第n
個數字,一部分不包含。
於是得到下面的公式:
stat(n, k) = stat(n-1, k) + F2(n, k)
到目前為止,我們就徹底形成了閉環,所有的轉換都可以得到了。
六、約數
其實這裡對約數計算有講究的。
一般人計算數字n
的約數個數的時候,直接從1
到n
開始一個一個的嘗試能否整除。
這樣的複雜度是O(n)
,很多時候是不可接受的。
而對於約數,有一個特徵:除了平方數約數,其他的肯定是成對出現的。
所以我們求約數的時候,不需要遍歷到n
,而只需要遍歷到srqt(n)
即可。
證明也很簡單,大家可以自己證明一下。
七、兩維陣列
在上面推理的時候,出現了兩個二維公式F2(n, k)
和stat(n, k)
。
就會有人說這個可能很大,儲存不下來怎麼辦?
這時候我們就需要具體看這兩個公式了。
F2(n, k) = stat(n-1, k-1) stat(n, k) = stat(n-1, k) + F2(n, k) = stat(n-1,k) + stat(n-1, k-1)
看之後有沒有發現什麼特徵?
對於F2(n)
,只與F2(n-1)
有關。
對於stat(n)
只與stat(n-1)
有關。
對於stat(n)
,純粹依賴於stat(n-1)
,前面的計算後面的,所以使用一個數組從小到大計算就可以了。
而對於stat(k)
,由於用到了stat(k)
和stat(k-1)
,所以對於約數k
我們需要從大到小遍歷即可。
為什麼大家可以思考一下。
八、最後
到這裡,這個問題我們就完美解決了。
這道題有意思的點在於,組合問題和數論問題結合起來了。
後面有時間了,也會多分享一些數論問題和組合問題。
本文首發於公眾號:天空的程式碼世界,微信號:tiankonguse-code。