• Tidak ada hasil yang ditemukan

Chapter 10 神秘專題討論(Special Subject)

N/A
N/A
Protected

Academic year: 2023

Membagikan "Chapter 10 神秘專題討論(Special Subject)"

Copied!
8
0
0

Teks penuh

(1)

Chapter 10  神秘專題討論(Special Subject)  

●Section 1 最近共同子祖先問題(Least Common Ancestors, LCA) 

§ LCA問題: 

給你一棵有根樹T,對於任意給定的兩個節點u, v,要求找出深度最深的節點x使得x 同時是u與v的祖先(ancestor)。而我們將定義LCA(u,v) = x。 

 

§ LCA問題的樸素算法: 

最簡單的想法當然就是列出u與v的所有祖先,並判斷深度最深的共同祖先是哪個,這 樣對於一個詢問的複雜度是O(n)的。如果要將所有pair的LCA都求出來(建表),就 至少需要花O(n3)的時間,當然不甚理想。 

 

§ 在線LCA問題的O(n2)‐O(1)算法: 

觀察上面問題可以發現,因為我們在建表時,兩次求LCA都是獨立的,我們應該更有效 的利用兩次求LCA時的關聯性,以便能減少時間。 

因此我們可以馬上想到,在u上的每個子孫節點v,與u的LCA會是u,所以我們可以 先對u向下作DFS求出LCA(u,u的子孫) = u。好了,不過對於非子孫節點的v要怎麼 作呢?我們可以發現到,我們在建上面的表時,意義實際是固定u跟祖先x,求v。 

沿著這個想法我們可以這樣做,假設u到root的路徑是(u,a1,a2,a3,...,root),那 我們可以先知道 

LCA(u的所有子孫,u) = u 

推得LCA(a1的所有子孫‐u的所有子孫,u) = a

再推得LCA(a2的所有子孫‐a1的所有子孫,u) = a     ... 

因此我們可以對所有的u去做類似DFS的操作,便可以求出所有節點對u的LCA,而且 只需要花O(n)的時間。 

因此我們可以用這種方法,在O(n2)的時間內求出all pair的LCA,以後對於每次回應 都可以O(1)回答。 

 

藉由以上的想法,我們可以推得一個類似的離線的算法──Tarjan算法(雖然個人認為一點 都不直觀啦= =a)。 

 

§ 離線LCA問題的Tarjan算法: 

我們先處理所有詢問,假設存在詢問LCA(uv),則將v放進集合Q(u)中,u放進集合 Q(v)中。 

(2)

其意思就是,當不存在詢問的兩個節點都在T[u]中時,T[u]中的所有點跟u其實是可 以看成同個點的。因此我們可以想到縮點操作的利器──Disjoint Set,可以幫助我們 達成求LCA的目的。 

 

TARJAN’SLCAALGORITHEM(u)  01 MAKE‐SET(u) 

02 FIND‐SET(u).ancestor  03 for every son v of 

04   TARJAN’s‐LCA‐ALGORITHEM(v)  05   UNION‐SET(u,v) 

06   FIND‐SET(u).ancestor  07 visit[u] ← true 

08 for every v in Q(u)  09   if visit[v] = true 

10   then print “LCA(u,v) = FIND‐SET(v).ancestor” 

  

可以發現到,因為做法都類似建表,而建表的下限卻一定是O(n2),所以想要更有效率的處 理問題,就不能把所有可能問題的表都建出來,而是建出部分問題的解答,詢問的時候再想 辦法去加速。 

 

因此為了更理想的做法,我們先來看另外一個問題:RMQ(Range Minimal Query)問題。 

 

§ RMQ問題(Range Minimal Query): 

給你一個長度為n的數列A以及許多詢問,對於每個詢問(i, j),回答x使得x=[i,j], 且對於所有的y=[i,j]有A[x]≥A[y],意思就是找出A[i..j]中最小數字的index值。

我們定義RMQ(i, j) = x。 

 

§ LCA轉化成RMQ 

是否記得我們在上圖論時曾經教過一個叫時間戳記的東西,而在教線段樹時,更是利用 時間戳記來將樹「壓平」,用時間軸上的區間來代表一個節點或子樹的性質。而我們這裡 也是要使用到這種樹的拜訪時間軸,來幫助我們轉化LCA問題。 

我們會先對這棵樹T開始作DFS,並且第一個紀錄的點是樹根,然後每走過一條邊時就 記錄下來走到的那個點,則因為每條邊會恰好走過兩次,會記錄下來2n‐1個點,我們 以A[1…2n‐1]表示。 

我們以H[i]表示節點A[i]的深度,並且我們記錄F[i]表示A中i第一次出現的位置。 

那麼我們要找LCA(uv)時,不妨設F[u]<F[v],則A[F[u]..F[v]]包含了DFS時第 一次走到u到第一次走到v的路徑。 

顯然,在這些點中最小深度的點即為uv的LCA,所以對T來說的LCA(u, v)即為對 H做RMQ(F[u],F[v])! 

(3)

  所以我們只需要一次DFS花O(n)的時間,就可以成功的將一個LCA問題轉化成一個RMQ 問題了。因此若我們能夠有效率的求出RMQ問題的一般解,就等於求出了LCA問題的一 般解。 

 

§ RMQ問題的遞推算法 – O(n2)‐O(1) 

顯然,RMQ(ii) = i,RMQ(ij)我們可以用下式推得: 

RMQ(ij) =  ( , 1) [ ( , 1)] [ ]

[ ( , 1)] [ ]

RMQ i j if A RMQ i j A j j if A RMQ i j A j

  

  

  

這樣可以在O(n2)的時間算出所有可能的詢問,然後用O(1)時間回答每個詢問。 

 同樣的問題再次發生,如果我們只是單純的建出全表,那麼無論如何都不可能降低到O(n2)

以下,因此我們必須要改變想法,想辦法只記錄部分,但是對於求出任一段的時間花費卻不 會太大。 

 

§ RMQ問題的線段樹算法 ‐O(nlgn) 

是否還記得之前線段樹時曾經說過,對於動態的RMQ問題可以利用線段樹來處理,每次 插入或修正一個數為O(lgn),詢問為O(lgn)(詳細作法請參考《線段樹》部分講義)。 

 

其實就時間上來說是很好的複雜度,但是基於人性(?),我們還會想要更好一點,而且又因 為我們這裡所說的RMQ為靜態而非動態,應該要有能更快、或更簡易一點的方法才是。 

 

§ RMQ問題的Sparse Table(ST)算法 – O(nlgn)‐O(1) 

首先我們觀察線段樹的算法,因為為了維持區間的性質,所以每次在求RMQ的時候總是 取許多不相交的區間做Union動作,然而既然我們只有靜態的要求,那麼維護區間性質 就不再那麼重要。 

更何況當i≤j≤k≤l時,必有RMQ(i,l) = min( RMQ(i,k) , RMQ(j,l) ),因此我們 在像線段樹一樣分成兩個區間去求最小值時,這兩個區間是可以重疊的!! 

又我們其實可以想到,如果令L = 詢問長度的話,則若L≥n/2,那我們其實可以先求出 所有[i,i+n/2]的RMQ值,以後可以便可以用O(1)查表得知。又如果n/2≥L≥n/22的 話我們其實可以先求出所有[i,i+n/22]的RMQ值……。以此類推,則我們可以先求出所 有[i,i+n/2k]的值,其中k=lgn。 

因此我們可以發現到,如果我們要求一段長度為 的區間的 時,只需要找到一數

(4)

由於[i,i+n/2k]區間其實是由[i,i+n/2k+1]跟[i+n/2k+1,i+n/2k]兩個區間合併而來,

因此只需要O(nlgn)的遞推時間便可求出所有所需的區間。 

由此便可以得出一個O(nlgn)‐O(1)的算法。 

但是你會發現n不為2的次方時要處理一些東西感覺很麻煩,所以我們可以假設 n=2lgn,反正這樣並不會影響時間複雜度,而且遞推也比較方便。 

 

以下便來統整一下上述作法: 

我們以d[ij]來記錄RMQ(ii+2j‐1),則顯然d[i, 0]=i,對於d[ij],我們顯 然有下列的關係式: 

d[ij] =  [ , 1] [ [ , 1]] [ [ 2 1, 1]]

[ 2 1, 1] [ [ , 1]] [ [ 2 1, 1]]

j

j j

d i j if A d i j A d i j

d i j if A d i j A d i j

      

        

  

所以我們可以用O(nlgn)的時間預處理出所有的d[ij]。 

對於一個詢問RMQ(ab),我們令k=[lg(ba+1)],則區間[aa+2k‐1]和區間[b‐2k+1,  b]皆為[ab]的子區間,並且合起來覆蓋了[ab],所以我們得到了下列的式子: 

RMQ(ab) =  [ , ] [ [ , ]] [ [ 2 1, ]]

[ 2 1, ] [ [ , ]] [ [ 2 1, ]]

k

k k

d a k if A d a k A d b k

d b k if A d a k A d b k

   

     

  

這樣對於一個詢問,只要O(1)的時間就可以給出回答。 

 

回顧我們剛才從LCA問題轉化過來的RMQ問題,會發現這樣的RMQ問題有個特性,就是相鄰 兩項的差都是±1,我們把這樣的問題稱為±1‐RMQ問題。 

   

§ ±1‐RMQ的O(n)‐O(1)算法! 

我們可以發現到如果有兩個數列A和B滿足:A[i]=B[i]+k,其中k是一個常數的話,

那麼顯然對於這兩個數列的RMQ問題是一模一樣的。 

但是你要怎麼快速判斷A,B兩個是一樣的呢?只要利用前項‐後項,得出的序列就會相 同了。所以我們知道,對於長度為n的±1‐RMQ問題,本質上不同的數列只有2n‐1個,

我們利用這個性質,設計出了一種更好的±1‐RMQ問題的算法。 

k = [lgn / 2],將數列A分割成n k/ 塊長度為k的段落。再利用 O(n)時間求出 各個段落的最小值組成另一個長度為n k/ 的序列A’。最後利用O(n k/ lgn k/ ) =  O(n)的時間做出A’的Sparse Table預處理。 

 

(5)

這樣一來對於任何長度為k的倍數的詢問都能在O(1)時間內回答了。但是對於長度不為 k的倍數的又怎麼辦呢?根據上文可以用O(2k)=O(n0.5)的時間枚舉出所有可能長度為k 的可能本質上不同的序列。 

我們使用O(2k)=O(n0.5)的時間窮舉出所有可能的長度為k的可能的本質上不同的序 列,然後對於每個序列,用遞推法用O(k2)=O((lgn)2)窮舉出所有可能詢問的所有答案,

並且全部存起來,總共花了O(n0.5(lgn)2)的時間。 

然後我們用O(n)的時間算出這n k/ 個段落分別為上面窮舉的哪一種序列,這樣我們就 可以用O(1)的時間回答出在第i個段落中第a個數到第b個數的最小值。 

對於任意的詢問RMQ(a,b),我們可以用O(1)的時間找出ab分別在哪個段落當中,

以及他們分別為那個段落中的第幾個數,而可以分成下面幾種狀況: 

1. 如果ab在同一個段落中,則可以直接用上面的預處理回答。 

2. 否則將[ab]分為三部分:a到其所在段落的結尾,b所在段落的開頭到b,和 這兩個段落之間的所有段落。前兩個可以直接用上述的預處理回答出,而第三 個可以用對A’上的RMQ詢問在O(1)的時間答出來,所以可以用O(1)的時間回 答出來一個詢問! 

 

因為我們已經會在O(n)的時間內把LCA問題轉化為±1‐RMQ問題,所以連帶的,我們也可以 在O(n)‐O(1)的時間內做出LCA問題! 

 § 一般性RMQ問題 

我們解決了±1‐RMQ後,自然的想到了一個更一般的問題:一般的RMQ問題,為了解決 這個問題,我們先來介紹一個特殊的資料結構:笛卡兒樹(Cartestian Tree)。 

 

§ 笛卡兒樹(Cartesian Tree) 

定義(Definition): 

笛卡耳樹是一種類似heap的樹狀結構。 

樹的建立是依據一個陣列A[1...n]。 

這棵樹的root是A[1...n]中最小的index值。 

而root的左子樹是A[1...root‐1]所形成的Cartesian Tree。 

而root的右子樹是A[root+1...n]所形成的Cartesian Tree。 

下圖即是一個陣列A的Cartesian Tree: 

(6)

建立(Construction): 

先建立一個stack維持說「這個stack裡的元素都是最右邊那條路上的結點」。 

從左開始往右把元素一個一個丟到樹裡面。每次要加入一個元素A[x]的時候就從這個

stack裡面去尋找。因為A[x]必定是要插在最右邊這條路上的。也就是他一定是某個節

點的右子樹不然就是root(這由定義很容易得到)。 

所以說每次我們只要考慮到最右邊那條路徑上的所有節點就OK了! 

假設存在一個stack中的元素k使得stack[k]<=A[x],則我們只需要將stack[k+1]

換成A[x](意思是把stack[k]的右子樹從stack[k+1]改成A[x])。而原本stack[k+1]

所形成的子樹全部放到A[x]的左子樹下就OK了!! 

 

但是假如不存在這個k的話。 那很明顯的就是將A[x]直接變成root。而A[0]下的子 樹變成A[x]的左子樹就OK了! 

所以很簡單的只需要掃過一遍陣列,就可以建出Cartesian Tree了!! 

因為每個數最多進入一次最右鏈退出一次最右鏈,因此均攤時間複雜度為O(n)。 

 

§ 一般性LCA與RMQ問題 

有了這個方法後,我們可以發現一件顯然的事:一個數列的RMQ即為他們在笛卡兒樹上 的LCA!既然我們可以用O(n)‐O(1)的時間解決LCA,就可以用O(n)‐O(1)的時間解決 一般RMQ問題! 

 

下面就來統整一下一般的LCA跟RMQ作法。 

 

     

(7)

●Section 2 啟發式搜索(Heuristic Search) 

  如果我們看到一個問題的時候,感覺很有想法,但是怎麼想都想不出來的時候,我們就 會嘗試使用暴力搜尋,可是對於那些感覺很有想法的狀態,難道我們就完全忽視它而完全的 暴力嗎?當然不是了,所以我們會將這些我們認為有用的啟發訊息,加到搜索過程中,使盲 目搜索「進化」成啟發式搜索,以下就來一一介紹: 

§ 估價函數 

  為了敘述方便我們將定義以下幾個函數: 

● s:代表問題的某種狀態。 

● h*(s):s到目標狀態的實際(最短)距離,雖然事先不知道。 

● h(s):s的估價函數,到目標距離的下界,也就是h(s)≦h*(s)。

● g(s):到達s狀態之前的代價,一般就代表s在搜索樹中的深度。 

● f(s):s的啟發函數,也就是到達目標的總代價的估計,明顯的,應該有 f(s)=g(s)+h(s)。

  一般來說我們在做啟發式搜索時,一個好的估價函數與搜索時間是呈指數型負相關的,

因此最重要的便是找到一個好的h估價函數,但是即使如此,就算是一個差的估價函數,仍 然還是會加快搜索速度,只是加快多少問題而已。 

  另外如果對於一個h函數對任意狀態s1和s2,滿足h(s1)≤h(s2)+cost(s1,s2),那麼 我們說h函數是單調(monotone)或相容的(consistent),簡單來說就是不要h減少的 太快。而由此可以得出對於這個h的啟發函數f,必有f(s1)≤f(s2),也就是f函數單調遞 增。如果能滿足這個條件,那f就可以算是一個好的啟發函數。 

  那麼有了啟發函數,接下來要怎麼進行搜索呢? 

§ 貪心搜索(Best‐First Search) 

  有一個很直觀的想法,便是改進BFS的過程,每次在擴展一個節點時,像BFS一樣照 著queue的順序擴展,只是擴展順序並不是FIFO的順序,而是照著queue中h值最小的開 始擴展(也就是使用priority queue)。但是這樣是有些問題的,因為h函數包含了很大 的不確定性,首先擴展到的結點未必能保證最優。主要是因為你忽視了到達這個狀態的花 費,因此要加入先前花費的部份,有了下面的改進算法。 

 

§ A*算法(A* Search Algorithm) 

  和Best‐First Search類似,只是priority queue中是按照f函數進行排序的,當 然可想而知這樣就必須判斷狀態重複的情況了,一但擴展到的狀態要加進priority queue 時發現已經在裡面了,就將在裡面的狀態f值更新成比較小的即可。可以證明,A*算法擴展 到的第一個目標結點一定是離初始狀態最近的一個,不過那不是這裡討論的重點XD。 

  而且明顯的,當h=0的時候,A*其實就是BFS,只是少了代價估計的部份,但是仍然是 能找得出最優解的。 

  但是你可以發現到,因為A*需要判重,所以空間需求量非常大,是指數級的,真是令

人哀傷Orz。不過如果不考慮這個的話,理論上他會是時間極優的。 

(8)

 

§ IDA*算法(Iterative Deepening A* Search Algorithm) 

  由前所述,IDA*是基於跌代加深的A*算法,對於f值大於d的結點全部的忽略掉,而 後不斷的增加d的深度。直到找到目標狀態時,當下的d值即是所要尋找的深度。 

 

§ 問題討論 

問題一《15‐Puzzle》(07建中校內賽Puzzle)(TIOJ1573,數字盤遊戲) 

給你兩個15‐Puzzle的狀態A,B,問你說從A到B至少要多少步。 

 

問題二《編輯書稿》(ACM11212,Editing a Book) 

你正在用電腦編輯一個文章,共有n(<10)個段落,現在你要重新安排這些段落的順序。

你可以使用剪下來剪下一個連續的段落,然後使用貼上貼到任何一個地方。 

給你一開始段落的排列方式,問你說排列成想要的另一個順序,最少要幾次操作? 

 

問題三《騎士覆蓋》 

在n*n的棋盤中放入盡可能少的騎士,使得所有的格子都能被攻擊到.騎士所在的格子不 會被自己攻擊到。 

 

§ 小結 

  啟發式搜索其實對於高中競賽並不會有很大的幫助,一般來說很少會利用到啟發式的搜 索(除非是像NOI每年都有一兩題爆搜題之類的),而到大學ACM‐ICPC雖然會增加多點接 觸但是用途還不能算是很大。通常都是要到外面的世界才會真正發現他的用處,基本上來說 啟發式搜索是人工智能(Artificial Intelligence)的一個很重要的基礎。所以還是需 要熟悉一下其用途與基本介紹才是。 

   

Referensi

Dokumen terkait

Minimum vertex cover & Maximum independent set 最小點覆蓋跟最大獨立集,在一般圖中都是 NPH 問題。他們的判定性問 題則是 NPC。然而,在二分圖中,這些問題是非常簡單的。 Minimum vertex cover 最小點覆蓋之意義乃是找出一個子點集,使得每一條邊至少有一端頂點

圖論 建國中學2012年資訊能力競賽培訓講義 - 06 • 如果圖 G 上有負環,且邊權最小的權值為 wmin,我們可以將所有的邊權加上 wmin 後當作邊權皆為正的圖去求單源最短路徑嗎? Bellman-Ford’s Algorithm 先定義一個動作叫做鬆弛 relax:我們用 dv代表目前從起點到 v 的最佳路徑,而如果 存在兩個點 u, v 使得 du