\( \newcommand{\ord}[1]{\mathcal{O}\left(#1\right)} \newcommand{\abs}[1]{\lvert #1 \rvert} \newcommand{\floor}[1]{\lfloor #1 \rfloor} \newcommand{\ceil}[1]{\lceil #1 \rceil} \newcommand{\opord}{\operatorname{\mathcal{O}}} \newcommand{\argmax}{\operatorname{arg\,max}} \newcommand{\str}[1]{\texttt{"#1"}} \)
顯示具有 Proof 標籤的文章。 顯示所有文章
顯示具有 Proof 標籤的文章。 顯示所有文章

2017年2月12日 星期日

[ Amortized analysis - Potential method ] 平攤分析 - 勢能方法

對於一個stack操作,pop和push都是$\ord{1}$的,這很好理解,現在定義了一個新的操作,pop_all,表示pop stack內的所有元素,顯然這是一個$\ord{N}$的操作。那我們進行一連串的push、pop、pop_all的複雜度上界是多少呢?

根據big-O的特性,因為pop_all是$\ord{N}$的,我們把每個操作都當作$\ord{N}$來看,執行$N$次操作的總複雜度就會是$\ord{N^2}$。這沒有錯,但怎麼想都覺得怪怪的,怎麼可能一直執行pop_all呢?執行一次pop_all之後stack就沒有元素了耶!

這三種操作不是平行的,而是互相影響的。換言之,你每次的push創造“機會”給pop和pop_all。pop和pop_all才能“消費”著這些機會,不存在無限制的消費。

因此這個複雜度是高估的,那要究竟怎麼真正去計算$N$次操作的總複雜度呢?

勢能方法

對於一個資料結構$DS$,我們定義$\Phi(DS)$表示$DS$的“勢能”,而且要保證在任何情況下$\Phi(DS) \geq 0$。

通常$\Phi(DS)$我們會定義他是$DS$的某個性質,像是元素個數啦,如果是二元樹的話可能是所有節點左子樹大小減右子樹大小的絕對值的總和啊、或是葉節點的個數啊...各種東西都可以當作是$DS$的勢能。

 那$\Phi(DS)$能用來幹甚麼?我們定義$\Phi_0$表示$DS$在還沒有進行任何操作時的勢能,假設有$N$個操作,第$i$個操作的時間花費為$t_i$,這個操作結束之後的勢能為$\Phi_i$,$i>0$,我們定義第$i$個操作的均攤時間$a_i=t_i+C(\Phi_i-\Phi_{i-1})$,這裡$C>0$是一個常數

可以發現總均攤花費時間:
$$
\sum^{N}_{i=1}a_i=\sum^{N}_{i=1}(t_i+C(\Phi_i-\Phi_{i-1})) \\
=(t_1+t_2+...+t_n)+C(-\Phi_0+(\Phi_1-\Phi_1)+(\Phi_2-\Phi_2)+...+(\Phi_{n-1}-\Phi_{n-1})+\Phi_n) \\
=\sum^{N}_{i=1} t_i +C(\Phi_N-\Phi_0)
$$
最後得到:
$$
\sum^{N}_{i=1}t_i=\sum^{N}_{i=1}a_i-C(\Phi_N-\Phi_0)
$$
這個特性告訴我們,只要好好選擇$\Phi(DS)$函數,就可以在$\ord{\sum^{N}_{i=1}t_i}$很難直接求的情況下透過$\ord{\sum^{N}_{i=1}a_i-C(\Phi_N-\Phi_0)}$求出$\ord{\sum^{N}_{i=1}t_i}$。

證明範例

有了勢能方法,可以很輕鬆的用它來計算一些資料結構操作的均攤複雜度。

以剛剛stack的例子來說,我們設定stack $S$它的勢能函數$\Phi(S)$為$S$的元素個數,可以確定$\Phi_0=0$且$\Phi(S) \geq 0$。

我們設一次的push、一次的pop花費一單位的時間,並設常數$C=1$,在第$i$次操作:
  • 如果是push操作的話$t_i=1$,stack的元素個數增加$1$
    因此$\Phi_i-\Phi_{i-1}=1$
    $a_i=t_i+\Phi_i-\Phi_{i-1}=1+1=2$
  • 如果是pop操作的話$t_i=1$,stack的元素個數減少$1$
    因此$\Phi_i-\Phi_{i-1}=-1$
    $a_i=t_i+\Phi_i-\Phi_{i-1}=1-1=0$
  •  如果是pop_all操作的話$t_i=n$,stack的元素個數減少$n$
    因此$\Phi_i-\Phi_{i-1}=-n$
    $a_i=t_i+\Phi_i-\Phi_{i-1}=n-n=0$
$a_i$的最大值是$2$,經過$N$次操作之後$\Phi_N-\Phi_0$的最小值為$0$
可以知道:
$$
\ord{\sum^{N}_{i=0}t_i}=\ord{\sum^{N}_{i=1}a_i-(\Phi_N-\Phi_0)}=\ord{2N+0}=\ord{N}
$$
因此經過$N$次stack的任何有效操作之後,總花費的時間為$\ord{N}$,這才是我們滿意的結果。

對了,通常來說$\Phi_0$都會是0,因此在大部分的情況下:
$$
\ord{\sum^{N}_{i=0}t_i}=\ord{\sum^{N}_{i=1}a_i}
$$
所以大部分的證明都會忽略掉$\Phi_N-\Phi_0$的部分

2016年12月7日 星期三

[ 0-1 Fractional Programming Dinkelbach algorithm ] 0-1分數規劃

【定義】

給你兩個數組,$benefit[i]$表示選$i$的利潤、$cost[i]$表示選$i$的花費。
如果選$i$,定義$x[i]=1$否則$x[i]=0$,通常題目會給你選擇的限制條件,像是必須是一顆生成樹之類的,這裡就把它忽略掉
我們的目標是要求$\frac{\sum{benefit[i] \times x[i]}}{\sum{cost[i] \times x[i]}}$的最大值。

【分析】

先不管最大化問題,令$\frac{\sum{benefit[i] \times x[i]}}{\sum{cost[i] \times x[i]}}=L$
可以把式子改寫成$(\sum{benefit[i] \times x[i]})-L \times (\sum{cost[i] \times x[i]})=0$
分離參數得到$\sum{(benefit[i]-L \times  cost[i]) \times x[i]}=0$
只要知道$L$,$(benefit[i]-L \times  cost[i])$是直接可以求出來的,先記他為$d(i,L)$吧
那可以設$f(L)=\sum{d(i,L) \times x[i]}$

我們的目標變成是要最大化$L$
那若存在一種$x$的選法使得$f(L)>0$能夠證明什麼呢?
$f(L)>0 \to \frac{\sum{benefit[i] \times x[i]}}{\sum{cost[i] \times x[i]}}>L$那表示存在另一個$L$會比現在的$L$還要好
那 $f(L)<0$?很明顯這種情況完全沒有意義,因為找不到這種$L$

最佳的$L$會讓所有$x$的選法都$f(L)\leq0$,只要找到這樣的$L$就說明他是最佳解了
也可以用類似的想法求最小化$L$,這裡就不贅述了。

【解法】

根據剛剛找出來的性質$f(L)$是單調性的,我們可以二分搜$L$,然後驗證是否存在一組解使得$f(L)>0$,這很簡單就不附code了。
另一個有效率的算法是Dinkelbach,他跟二分搜不一樣的地方是他要去求解,在驗證解跟求解的複雜度一樣的時候Dinkelbach是會比較快的,但有時候求解的複雜度會比驗證解的複雜度還要高,因此要看情況使用
以下附上虛擬碼:

常見的題型有:最優比率環、最優比率生成樹、最大密度子圖(有flow解)等

2015年7月25日 星期六

[ Palindromic Tree ] 回文樹(回文自動機)

回文樹是去年新出的資料結構,由一個俄羅斯codeforces的紅人Mikhail Rubinchik在2014年發明的(http://codeforces.com/blog/entry/13959),雖然名子直接翻譯叫回文樹,但他長的比較像一個自動機,所以也有很多人翻回文自動機。
在構造它之前,我們必須先證明一個長度n的字串其不同的回文子字串個數<=n,什麼是不同的回文子字串?就是長的不同的回文子字串,及把出現位置不同但形狀一樣的回文子字串當做是同樣的子字串。

證明:
  1. 由左往右一個一個增加字符,則新產生的回文子字串其結尾一定是當前增加的字符x
  2. 考慮以當前位置為結尾的最長回文x......x,顯而易見的,若產生除了x......x之外其它的回文子串,則必定被x......x所包含
  3. 若增加字符x後,產生了除了x......x之外其它的回文子串,則根據回文的性質,其一定早已在x......的部分理出現(例:假設S是對稱點x...S.xax,因為回文的性質所以S的另一邊也會出現相同的字串xax.S.xax)
  4. 因此每增加一個字符最多只會產生一個新的回文子字串,而長度為n的字符串最多只會產生n種不同的回文子字串
接下來進行回文自動機元素的詳細解說:

回文自動機包含以下元素:
  1. 狀態St,所有節點的集合,一開始兩個節點,0表示偶數長度串的根和1表示奇數長度串的根
  2. last 新增加一個字符後所形成的最長回文串的節點編號
  3. s 當前的字符串(一開始設s[0]=-1(可以是任意一個在串S中不會出現的字符))
  4. n 表示添加的字符個數

每個節點代表一個不同的回文子字串,我們在每個節點會儲存一些數值:
  1. len 表示所代表的回文子字串長度
  2. next[c] 表示所代表的回文子字串在頭尾各增加一個字符c後的回文字串其節點編號
  3. fail 表示所代表的回文子字串不包括本身的最長後綴回文子串的節點編號
  4. cnt(非必要) 表示所代表的回文子字串在整體字串出現的次數(在建構完成後呼叫count()才能計算)
  5. num(非必要) 表示所代表的回文子字串其後綴為回文字串的個數
注意一開始必須先將狀態初始化St[0].len=0,St[1].len=-1,last=0,s[0]=-1,n=0

關於構造方法及圖片說明請參考:http://blog.csdn.net/u013368721/article/details/42100363
概念請參考:http://blog.csdn.net/MaticsL/article/details/43651169
證明請參考:http://yqcmmd.com/index.php/2015/03/30/746/

最後提供模板(使用STL vector),因為St的元素在解題的過程中常會被用到所以就不封裝了:

2015年1月17日 星期六

Randomized Binary Search Tree 平均深度證明

一棵Randomized Binary Search Tree ,平均深度為2logn
其每個節點成為該顆子樹的根的機率有1/(該顆子樹的大小+1)
對於[分裂/合併式][Split / Merge] Randomized Binary Search Tree 亦同
而其等價於在BST中進行隨機插入
這裡提供嚴謹的證明 連結
以下簡略的證明原自於陳柏叡大大(數奧國手)親筆所寫:

現在總共有n個數,要證說對他隨機排序之後一個一個插入BST得到的平均最大深度=2logn。
那看任意一個數x,他的祖先個數就是它的深度,所以只要算x的祖先個數的期望直。
這只要算,對x以外的每個數y,y當x的祖先的機率是多少,再把他們加起來即可。

但注意到,假設在這n個數(由小到大)排序好的序列裡面,x 排在第 i 個, y 排在第 j 個(  y 可以 <x,所以 j 可以 < i  ),那麼落在 [ i , j ] ( 或 [ j , i ] )這個區間裡的所有數, y 是第一個被插入BST的(否則會和 y 是 x 的祖先矛盾),而這件事情發生的機率為 1/(  | i - j | +1 ) 。
也就是,如果一個數 y 和 x 在排序好的序列裡面的坐標差 = d 的話,那麼 y 當 x 的祖先的機率就是 1/(d+1)。

所以如果 x 是排序好的序列裡的第 i 個,那所求是
1 / i + 1 / ( i - 1 ) + ... + 1/2 + 1/2 + 1/3 + ... + 1/( n - i + 1 )
<= 2 ( 1/2 + 1/3 + ... + 1/n )
約 = 2logn