筆記:《挑戰程式設計競賽(第2版)》(1)

漆楚衡發表於2015-06-03

筆記:《挑戰程式設計競賽(第2版)》

遙想一年半前買的這本書,結果拖延了一個學期才看,收穫很大,不過有很多地方看不懂。最近開始準備藍橋杯(是啊,比賽不到一個星期了……),又把這本書拿出來看。

首先感覺自己之前白看了……不過後來又慢慢還能回想起來一些,又一次收穫很大。

也有比較開心的,一年前的很多完全看不懂的地方現在居然看得行雲流水。應該要歸功於自己理論背景的加深吧——自動機、組合數學、高數。

隔上一段時間回望自己,發現原來當時的自己那麼嫩。而今天能有所察覺,算是有進步吧。

不過還是有一些地方要花時間去想,把它們記下來。


Page 16 : 三角形-註腳①

本題還有O(nlogn)時間更高效的演算法,留給有興趣的讀者思考。

(1)先對n根棍子按長度排序,得到序列L

(2)從L中取最長的三根

  • 如果能組成三角形則輸出,結束
  • 否則刪除最長那根,轉(2)

這裡如果最長和兩根次長不能組成三角形,則最長那根無論如何都不能跟其它棒子組成三角形了,所以刪掉。


Page 47 : Fence Repair

作者直接給出來了要用Huffman樹,沒有解釋原因。

我自己思考了一下(午),也算是給出個解釋。

觀察題目,從樹的角度考慮,將切割過程看成一棵樹,根是原始木板,一刀下去以後得出兩個小木板就是它的兩個子節點,再切子節點……

最後的葉節點就是最終所要求的木板(L1...Ln)。

將各節點所代表的木板的長度作為節點的權

題目所求的開銷就是非葉子節點權值之和

觀察非葉子節點的組成,會發現其實每一個葉子節點在其每一個祖先節點上佔有其權值。

也就是說,權值之和等於

sum( weight(Li) * depth(Li) ) (1 <= i <= n)

最短的板與次短的板的節點應當是兄弟節點

為什麼?

其中depth(Li)是Li所在深度,以depth(root)為0。

如果我們將depth(Li)看成編碼長度,而weight(Li)看成字母權重,就可以理解這裡為什麼要用Huffman樹了。

那麼新的問題是,我忘了Huffman樹的證明了,不過現在時間緊急,留待以後去查吧。


Page 57 : 最長公共子序列問題的註腳①

如果稍微思考一下就能發現s[i + 1] = t[j + 1]時,只需令dp[i + 1][j + 1] = dp[i][j] + 1就可以了。

我思考了一下(午)。

用反證法,設:

dp[i + 1][j] - dp[i][j] >= 2

根據轉移方程(Page 57頂),我們可以替換dp[i + 1][j],有:

dp[i][j - 1] - dp[i][j] >= 1        (與定義不符)

dp[i][j] - dp[i][j] >= 2            (與定義不符)

dp[i + 1][j - 1] - dp[i][j] >= 2

對於這種情況,我不知道如何直接表示其不行,不過可以繼續拆解它,有:

dp[i][j - 2] - dp[i][j] >= 1        (與定義不符)

dp[i][j - 1] - dp[i][j] >= 2        (與定義不符)

dp[i + 1][j - 2] - dp[i][j] >= 2

可以歸納,這樣一直持續下去,可得

dp[i + 1][0] - dp[i][j] >= 2        (與定義不符)

即必有

dp[i + 1][j] - dp[i][j] < 2

同理,有

dp[i][j + 1] - dp[i][j] < 2

Page 68 : 多重集合數

這個題,因為公式比較複雜(對我來說),我的感覺是看公式不如看程式碼清晰。

其實題目的思想比較簡單,首先可以簡單理解O(nm^2)的演算法,然後觀察遞推關係,發現計算dp[i + 1]中的每個值得計算都是用dp[i]的一個固定長度的視窗的累和,所以用滑動視窗思想降低複雜度。剩下比較麻煩的是一些邊界條件處理。


Page 88 : 食物鏈

這個題比較抽象,難到我了,不過經過不懈的努力,還是理解了。

對我來說,題目最大的思維難點是為何要用三個元素表示一隻動物。

這個想通了非常簡單,列舉。

題目中所遭遇的困難是在處理過程中沒有辦法給予任意元素一個確定的值。

設想在處理過程中,一般地,我們有若干個集合。

相同集合的元素之間都有關係,不同集合中的元素都沒有關係。

此時如果要對所有元素確定性賦值,元素只要滿足其所在集合的要求就行了,其他集合對這個元素沒有要求。

但在處理過程中會發生集合合併:合併有確定賦值的集合A,B,有A中元素a,原先B對a不做要求,但是現在要要求a取特定值,如果原先A對a的賦值不滿足,則現在要跟據a重算A中所有元素的賦值以適應B。這一過程很複雜。

所以需要不確定的賦值,也就是列舉所有可能性。再看剛才的情形,某種確定的B對A中的a有確定的要求,只要將對應的a賦值與此種B關聯就好了。


Page 113 : 線段上格點的個數

為何是最大公約數?(雖然給了個圖解釋,不過我還是沒有直接領悟。)

給定一條兩端點都在整數座標上的斜線,有其在x軸上的投影長度為a,在y軸上的投影長度為b,a與b必為整數。

題目所求,即是最大的c,使得

a = da * c
b = db * c

成立。da,db為整數。

這也就是求a,b的最大公約數啦。


Page 119 : 埃氏篩法

受到下一小節(區間篩法)的啟發,這裡有一些細節。

// 摘自書中Page 119
int primes[MAX_N];
bool is_prime[MAX_N + 1];

int sieve(int n) {
    int p = 0;
    for (int i = 0; i < n; i++) is_prime[i] = true;
    is_prime[0] = is_prime[1] = false;
    for (int i = 2; i <= n; i++) {
        if (is_prime[i]) {
            prime[p++] = i;
            for (int j = 2 * i; j <= n; j += i)
                is_prime[j] = false;
        }
    }
    return p;
}    

注意到對於某個i和任意k(k < i)。

合法範圍內的k * i必然已經被篩過了。因為k本身是素數或k的某個素因子。

所以對於內層迴圈j完全可以由i * i開始。

同時,可以發現,對於列舉倍數的目的,i的上限可以是sqrt(n)。此時,(sqrt(n), n]範圍內的素數已被篩出。(這一範圍內的合數必然有一個小於等於sqrt(n)的質因子。

相關文章