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

漆楚衡發表於2015-06-08

Page 134 : Millionaire

這個題,以我的智商,一開始沒有很好的理解他的題解。之後看程式碼就毀了,程式碼裡第一重迴圈示意的方向是反的,雖然只要輪數對了就好,不過這樣程式碼阻礙理解題解,我看了好久才反應過來。

程式碼倒數第三行

int i = (ll)X * n / 100000;

中的

X * n / 10000000

其實是

X / (1000000 / n)

也就是將100000分成`n + 1(n = 2 ^ m)`份,就可以得出X在其中是哪一份(的索引)。

接下來的一個細節問題是搜尋的邊界,程式碼裡有

int jub = min(i, n - i)

這裡jub就代表了當前錢數的投注(搜尋)區間[i - jub, i + jub]

取i和n - i中的小值。

前一個很好理解:不能投注比自己當前錢數還多的錢。

不過後一個就不太明顯了。

首先知道動態規劃陣列(prv,nxt)的右邊界(即下標n)代表了錢數超出1000000的所有部分。

邏輯上,此時i + jub == n對應多個i - jub,即可以投注多種錢數區間,贏了以後都轉移到下標n,而輸了則轉移到對應區間。

所以右邊界乍看上去似乎不太合適,沒有列舉所有可能的輸錢區間(對應於i + jub > n的jub)。

這裡給出一個解釋:

容易理解,整個動態規劃陣列是單調非嚴格遞增的(持有的錢多更容易贏)。

所以對於剛才所說的多個i - jub對應一個i + jub == n

prv[i + jub]此時是定值。

其實只需要看最大的一個 prv[i - jub]。當jub取n - i時,有n關於i的對稱點(即i - jub的最大值)i - (n - i)


Page 181 : 樹狀陣列的區間操作

樹狀陣列可以支援高效地查詢和修改數列的字首和。不過如果加入區間修改,問題就變複雜了。顯然不能對區間逐個修改。

考慮對於僅在一個區間[l, r)上做了修改(增加x)的情況,此時,可以發現,對於l之前的部分,字首和不變。對於r之後的部分,字首和增加了一個常數(r - l) * x

關鍵是對於[l, r)之間的部分,字首和的增加量是x的函式,對於l <= j < r,有(j - l) * x

擴充到多個區間時,我們有多個xi分別對應[li, ri)區間。

讓我們化簡一下問題,既然對於查詢在[li, ri)之外的部分,我們都可以用常數表示其增量。

那麼我們就關注對於特定的位置a,查詢[0, a)的和,我們只關注那些使li <= a < ri[li, ri)

現在的問題是如何高效地表示

(a - li) * xi

如果我們將所有的li向左延伸到0(對應地,需要減去常量li * xi),就有

a * xi

問題變成了如何高效地求解∑xi

答案可以是另一個樹狀陣列。

整理一下

有一個樹狀陣列bit0, 我們希望對其實施區間修改操作。

為此,需要引入另一個樹狀陣列bit1。

對bit0上一個區間[l, r)增加x可以表示為

  • 在bit0的l處增加-(l * x)
  • 在bit1的l處增加x。

當查詢位置a越過了r時,我們還要撤銷x的效果

  • 在bit1的r處增加一個-x

這樣就撤銷了位置相關的修改,但要在bit0上加上常量增量,之前已經增加過-(l * x)(實際上是減少的意思)

  • 在bit0的r處增加一個r * x

更一般地,如果操作得到的結果可以用i的n次多項式表示,那麼就可以使用n + 1個樹狀陣列來進行維護了。

這裡還是不明白,i的1次多項式的實際意義可以是區間修改,那麼i的更高次多項式又可以具有怎樣的意義呢?


Page 194 : 計算DAG的最短路

計算DAG的最短路不需要使用Dijkstra演算法,可以簡單地通過DP求解。

對於這句話我的理解如下:

考慮Dijkstra演算法所能求解範圍——邊權為正的圖,DAG與其的區別在於前者可以有環路。

Dijkstra演算法處理環的方法是應用問題的貪心性,每次選取從起點出發最近抵達的點,這個點不可能有其它更短的路徑了。

DAG因為不存在環路,只要求解了一個點的所有前驅(沒有環才有這個定義)的最短路,自然就可以求解這個點最短路。

所以對點的選取順序要求變弱,只要按照(任一)拓撲序選點求行了。

另外,根據上下文,這裡可能想強調的是具有規則分層結構的DAG,對於這種DAG可以容易地寫出動態規劃演算法。比如本題的圖就很規則,按照集合的大小(遞減)分出不同層次,逐層求解。

相關文章