---
# System prepended metadata

title: Intro. to Algorithm HW3
tags: [Intro. to Algorithm]

---

# Intro. to Algorithm HW3
###### tags: `Intro. to Algorithm`
- [name=曾文鼎 0716023]

## 1
以下說明皆為 1-based 。

定義二維數列 $a_{(i,j)}$ 如下，其中 $i\in\left[1,n\right], j\in\left[1,\sqrt{n}\right]$ 。 $$a_{(i,j)}=\begin{equation}\begin{cases}
1 &\text{if}~i=0~\text{or}~s_i=j \\
a_{(i-1, j)}+1 &\text{otherwise}
\end{cases}\end{equation}$$ 

定義 $\Phi$ 為 potential function。 $$\Phi(s_i)=\begin{equation}\begin{cases}
0 & \text{if}~i=0 \\
\sum_{x=1}^{\sqrt{n}}a_{(i,x)} & \text{else} 
\end{cases}\end{equation}$$

定義 $c_i$ 為尋找 $s_i$ 的predecessor 實際花費的成本。 $$c_i = \begin{equation}\begin{cases} 
i-p &\text{where}~s_p~\text{is the predecessor of}~s_i \\
i-1 &\text{if}~s_i~\text{has no predecessor}
\end{cases}\end{equation}$$

因為 $$\begin{align}
\widehat{c_i} &= c_i + \left[\Phi(s_i)-\Phi(s_{i-1})\right] \\
&= c_i + \left[\sqrt{n} - c_i\right] \\
&= \sqrt{n}
\end{align}$$

故有 $$\begin{align}
&& \sum_{i=1}^{n}c_i + \Phi(s_n)-\Phi(s_0) = \sum_{i=1}^{n}(c_i + \Phi(s_n)-\Phi(s_0)) = \sum_{i=1}^{n}\widehat{c_i} &= n\left(\sqrt{n}\right) = n^{1.5} \\
&\implies & \sum_{i=1}^{n}c_i + \Phi(s_n) + \Phi(s_0) &= n^{1.5} \\
&\implies & \sum_{i=1}^{n}c_i + \Phi(s_n) &= n^{1.5} \\
&\implies & \sum_{i=1}^{n}c_i &< n^{1.5}
\end{align}$$

故此程式的複雜度為 $O\left(n^{1.5}\right)$。

## 2
### a
下表列出操作各種資料結構的均攤複雜度。

|| Array | Binary heap | Fibonacci heap |
|---|---             | ---         | ---            |
| Insert | $O(1)$ | $O(\log n)$| $O(1)$          |
| Decrease-Key | $O(1)$ | $O(\log n)$| $O(1)$          |
| Extract-Min | $O(n)$ | $O(\log n)$| $O(\log n)$     |

定義 $T(G)$ 為找出 $G$ 的最小生成樹的時間成本。整個程式的複雜度為 $$\begin{align}
T(G) &= O(E)\cdot\text{Insert}+O(V)\cdot\text{Extract-min} + O(E)\cdot\text{Decrease-Key} \\
&= O(n\log n)(\text{Insert}+\text{Decrease-Key}) + O(n)(\text{Extract-min})
\end{align}$$
* 若使用 Array ，複雜度為 $T(G)=O\left(n\log n+n^2\right) = O\left(n^2\right)$ 。
* 若使用 Binary heap ，複雜度為 $T(G)=O\left(n\log^2 n+n\log n\right) = O\left(n\log^2 n\right)$ 。
* 若使用 Fabonacci heap ，複雜度為 $T(G)=O\left(n\log n+n\log n\right) = O\left(n\log n\right)$ 。

因此使用 Fabonacci Heap 最優。

### b
對於一連通圖 $G=(V,E)$ ， Binary heap 較 Fabonacci heap 佳若且唯若 $$\begin{align}
&& T_{~\text{Binary heap}}(G) &\leqslant T_{~\text{Fabonacci heap}}(G) \\
&\implies & O(E\log n)+O(n\log n) &\leqslant O(E)+O(n\log n)
\end{align}$$

解不等式，得知若且唯若圖 $G=(V,E)$ 的邊的數量少於等於 $O(n)$ 時， $G \in \mathcal{H}$ 。亦即 $$G=(V,E)\in\mathcal{H} \Longleftrightarrow E\leqslant O(n)$$

## 3
存在。

| 原圖 | DFS Tree | BFS Tree |
| --- | --- | --- |
| ![](https://i.imgur.com/5kDQOQj.png =x160) | ![](https://i.imgur.com/YcfnhBG.png =x160) | ![](https://i.imgur.com/phTkrpg.png =x160) |

## 4
### a
把第 9 行的 $n-1$ 改成 $n$ 。

### b
一連通圖 $G=(V,E)$ 能夠藉由壞掉的 Algorithm 2 算出正確的最小生成樹，若且唯若 $$\begin{align}
&& \text{dis}[i][j] &= \min\left(\text{dis}[i][k]+\text{dis}[k][j], \text{dis}[i][j]\right) \\
& \Longleftrightarrow & \text{dis}[i][j] &\leq \text{dis}[i][k]+\text{dis}[k][j]
&& k=n, \forall i\forall j \in[1,n]
\end{align}$$

亦即節點 $V_n$ 並不在任意兩其他節點的最短路徑上，此時 $V_n$ 為最小生成樹的樹葉。故 $$G=(V,E)\in\mathcal{H} \Longleftrightarrow V_n\in \text{Leaf}(G)$$ 

## 5
對於公差為零的等差數列，可以預先透過求眾數演算法得知，因此以下僅討論公差非零的情況。

#### 步驟
1. 給定序列 $s$ ，隨意選一個 $s_i$ ，期望 $s_i \in s'$ （$s'$ 表示 $s$ 的最長等差子序列）。若存在 $|s'|\geqslant |s/2|$ ，顯然這樣的期望有至少 $0.5$ 的機率實現。

2. 對於給定的 $s_i$ ，往後依序選取 $s_{i+1}, s_{i+2}, ...$ ，期望找到最小的正整數 $x$ 使得 $s_{i+x}\in s'$ ，藉以找到公差 $d=s_{i+x}-s_{i}$ 。若存在 $|s'|\geqslant |s/2|$，則 $s_{i+x}$ 為最小的 $x$ 使得 $s_{i+x}\in s'$ 的機率顯然至少為 $0.5^x$ 。

3. 對於給定的 $d$，在 $O(n)$ 的時間內確認這樣的 $s'(|s'|\geqslant|s|/2)$ 是否存在。

若存在 $|s'|\geqslant |s/2|$ 只要執行步驟 1. 達 $10$ 次，就能有至少 $1-0.5^{10} > 0.999$ 的機率猜到一個 $s_i\in s'$ 。對於每個步驟 1. 選取的 $s_i$ ，只要執行步驟 2. 達 $10$ 次，就有 $$\sum_{x=1}^{10} 0.5^x > 0.999$$ 的機率找到公差 $d$ 。

綜上所述，若存在 $|s'|\geqslant |s/2|$ ，至少有 $0.999^2>0.99$ 的機率找到公差 $d$ ，並且耗時 $O(1)$。檢查公差 $d$ 耗時 $O(n)$ ，故此演算法的複雜度為 $O(n)$ 。

此演算法有以下結論：
* 若序列 $s$ 存在等差子序列 $s'(|s'|\geqslant|s|/2)$，此演算法有至少 $0.99$ 的機率回傳 true 。
* 若序列 $s$ 不存在等差子序列 $s'(|s'|\geqslant|s|/2)$，此演算法總是回傳 false 。

#### Psuedo code 
```
/** 
 * Queries if there exists an arithmetic subsequence of seq 
 * whose length is not less than n/2 and common diffrerence 
 * is d. The correct rate is 100% and the time complexity 
 * is O(n).
 */
function judge(seq, d) {
    // Generates a hash set containing all the elements in s.
    let hs = new HashSet;
    foreach (e in seq) { insert e into hs; }
    
    for (i=1; i<=|seq|; i++) {
        if ((seq[i] - d) is in hs) continue; // O(1)
        let e = seq[i];
        let counter = 0;
        // Note this while loop runs n times OVERALL
        // regardless of the outer for loop. Hence 
        // the complexity is O(n).
        while ((counter < |seq|/2) and (e is in hs)) {
            e += d;
            counter++;
        }
        if (counter >= |seq|/2) return true;
    }
    return false;
}

/**
 * Queries if there exists an arithmetic subsequence of 
 * s whose length is not less than n/2. If such subsequence
 * exists, this function returns true with probability at 
 * least 99%; if not exists, this function always returns 
 * false. The time complexity is O(n).
 */ 
function judge(s) {
    let m = mode of s;                   // O(n)
    if (count of m in s >= |s|/2) {      // O(n) 
        // Exists an arithmetic subsequence whose common 
        // difference is 0.
        return true;
    }
    
    // Common difference must not be 0.
    for (i=1; i<=10; i++) {             // O(1)
        let a = random integer in [1, |s|-10]
        for (j=1; j<=10; j++) {         // O(1)
            // Assumed that index is always in the bound.
            // Since n is big.
            let d = s[a+j] - s[a];
            if (judge(s, d)) {          // O(n)
                return true;
            }
        }
    }
    
    return false;
}
```

#### C++ code
以下給一個跑得動的 C++ (hackmd.io/@Hyperbola/algo-hw3)。
```cpp
#include <bits/stdc++.h>
using namespace std;

const int MAX_NUM = 50000;
const int MAX_DIFF = 100;

bool judge(vector<int>& s, int d) {
    int n = s.size();
    // Generates arith hashset containing all the elements in s
    unordered_set<int> ss(s.begin(), s.end());
    for (int i = 0; i < n; i++) {
        if (ss.count(s[i] - d) /* O(1) */) continue;
        int e = s[i];
        int counter = 0;
        while (2 * counter < n and ss.count(e)) {
            e += d;
            counter++;
        }
        if (2 * counter >= n) return true;
    }
    return false;
}

bool judge(vector<int>& s) {
    int n = s.size();
    assert(n >= 20);

    const int C = 10;
    for (int i = 0; i < C; i++) {  // O(1)
        int arith = rand() % n - 10;
        for (int j = 0; j < C; j++) {  // O(1)
            int d = s[arith + j] - s[arith];
            if (judge(s, d)) {  // O(n)
                return true;
            }
        }
    }

    return false;
}

vector<int> positive_dataset(int n) {
    vector<int> arith, non_arith;
    int d = rand() % MAX_DIFF;
    if (d == 0) d = 1;
    int a0 = rand() % MAX_NUM;
    for (int i = 0; 2 * i < n; i++) arith.push_back(a0 + i * d);
    while (arith.size() + non_arith.size() < n) { 
        non_arith.push_back(rand() % MAX_NUM);
    }
    vector<int> sf;
    for (int i = 0; i < n; i++) {
        sf.push_back(i < arith.size() ? 1 : 0);
    }
    random_shuffle(sf.begin(), sf.end());
    vector<int> v;
    for (int i = 0; i < n; i++) {
        if (sf.back() == 1)
            v.push_back(arith.back()), arith.pop_back();
        else
            v.push_back(non_arith.back()), non_arith.pop_back();
        sf.pop_back();
    }
    reverse(v.begin(), v.end());
    return v;
}

vector<int> negative_dataset(int n) {
    vector<int> ret;
    for (int i = 0; i < n; i++) ret.push_back(rand() % MAX_NUM);
    return ret;
}

int main() {
    cout << "A postitive dataset or negative (P/N): ";
    string reply;
    cin >> reply;
    char r = toupper(reply[0]);
    if (r != 'P' and r != 'N') exit(1);

    cout << "Size of dataset (recommended 100000, at least 20): ";
    int n;
    cin >> n;
    if (n < 20) exit(1);

    srand(time(0));
    vector<int> dataset = 
        r == 'P' ? positive_dataset(n) : negative_dataset(n);
    cout << (judge(dataset) ? "True" : "False") << endl;
    return 0;
}
```

## 6
#### 作法
定義
* $O(D)$ 表示光碟 $D$ 的圓心。
* $C(P,r)$ 表示以 $P$ 為中心、半徑為 $r$ 的圓。
* $M(B)$ 表示二分圖 $B$ 的 Maximum vertex cover 的數量。

以下給出 $O(n^3)$ 的解法。
1. 枚舉光碟對。對於每對光碟對 $(D_1, D_2)$ ，若 $l=\overline{O(D_1)O(D_2)} \leqslant 2$ ，令 $I=C(O(D_1),l)\cap C(O(D_2),l)\neq\phi$ 。此步驟複雜度 $O\left(n^2\right)$ 。
2. 對於一個給定的 $I$，過 $\overline{O(D_1)O(D_2)}$ 將 $I$ 分成兩半 $I_1,I_2$ 。掃描平面上所有光碟的中心，取得兩集合 $H_1, H_2$ ，其中 $H_i=\{D:O(D)\in I_i\}$ 。然後令 $H=H_1\cup H_2$ 。此步驟複雜度 $O(n)$ 。
3. 若 $|H| \geqslant 2k$ ，表示必定存在 $k$ 個光碟其兩兩相交，此演算法回傳 true 並立即結束。
4. 對 $I$ 內所有未相交的光碟對建邊，將會得到二分圖 $B=(V,E)$ 。這是因為 $I_1$ 內的任意光碟對 $(D_1,D_2)$ 必有 $\overline{O(D_1)O(D_2)}\leqslant l\leqslant 2$ 。 $I_2$ 同理。 此步驟複雜度 $O(\left|H\right|^2)=O\left(k^2\right)=O\left(\log^2 n\right)$ 。
5. 計算 $M(B)$，表示存在 $M(B)$ 個光碟兩兩相交。若 $M(B)\geqslant k$ ，此演算法回傳 true 並立即結束。此步驟可以透過 Ford-Fulkerson 計算 Maximum flow 在 $O(EV)\leqslant O(\log^2 n)$ 內完成。
6. 枚舉完所有光碟對後，回傳 false ，演算法結束。

注意步驟 3. 在 $|H| \geqslant 2k$ 時結束演算法。這是因為對於任意二分圖 $B=(V,E)$ 恆有 $M(B) \geqslant |V|/2$ 。

此演算法的複雜度為 $$O(n^2)\left[O(n)+O(\log^2n)+O(\log^2n)\right] = O(n^3)$$

#### Pseudo code
```
/**
 * Queries if there exists k disks within diskset such
 * that they mutually intersect. The time complexity is
 * O(n^3).
 */
function judge(diskset, k) {
    foreach (diskpair(d1, d2) in diskset) { // O(n^2)
        let p1 = center_of(d1), p2 = center_of(d2);
        let l = dist(p1, p2);
        if (l > 2) continue;
        let i = intersect_of(
                    circle(p1, l),
                    circle(p2, l)
                );
        let h = {};
        foreach (disk in diskset) { 
            if (center_of(disk) is in i) {
                insert disk into h
            } 
        }
        
        if (|h| >= 2*k) return true
        let b = empty graph;
        foreach (diskpair(u1, u2) in h) {
            if (dist(center_of(u1), center_of(u2)) > 2) {
                insert vertex(u1) into b
                insert vertex(u2) into b
                insert edge(u1, u2) into b
            }
        }
        
        let m = maximum_vertex_cover_of(b);
        if (m >= 2*k) return true;
    }
    return false;
}
```