---
# System prepended metadata

title: 2020q3 Homework8 (quiz8)
tags: [進階電腦系統理論與實作, ' NCKU Linux Kernel Internals', ' 作業系統']

---

---
tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統
---

# 2020q3 Homework8 (quiz8)
contributed by < `RusselCK` >

###### tags: `RusselCK`

> [ dynamic programming & bitwise 操作 ＆ linked list ＆ 非連續記憶體操作 練習題](https://hackmd.io/@sysprog/2020-quiz8)

## Q1. LeetCode [1494. Parallel Courses II](https://leetcode.com/problems/parallel-courses-ii/) 
Given
* the integer `n` representing **the number of courses** at some university labeled from `1` to `n`
* the array dependencies where `dependencies[i] = [xi, yi]`  represents a **prerequisite relationship**
    * that is, the course `xi` must be taken before the course `yi`.
* **In one semester you can take at most `k` courses** as long as you have taken all the prerequisites for the courses you are taking.

Return **the minimum number of semesters** to take all courses.

Example:
![](https://i.imgur.com/JLdYvVi.png)
```
Input: n = 5, dependencies = [[2,1],[3,1],[4,1],[1,5]], k = 2
Output: 4 
```
### 遞迴解 (**`recursive-nos1.c`**)

```c=
#include <string.h>  
#include <limits.h>

#define min(a, b) (((a) < (b)) ? (a) : (b))
static void solve(int i, int s, int k, int n, int s0, int s1, int *dp)
{
    if ((k == 0) || (i == n)) {
        dp[s0 | s] = min(dp[s0 | s], dp[s0] + 1);
        return;
    }

    solve(i + 1, s, k, n, s0, s1, dp);

    if ((s1 >> i) & 1)
        solve(i + 1, s | 1 << i, k - 1, n, s0, s1, dp);
}

int minNumberOfSemesters(int n,
                         int **dependencies,
                         int dependenciesSize,
                         int *dependenciesColSize,
                         int k)
{
    const size_t cap = 1 << n;
    int pre[cap];
    memset(pre, 0, sizeof(pre));
    for (int i = 0; i < dependenciesSize; i++)
        pre[dependencies[i][1] - 1] |= 1 << (dependencies[i][0] - 1);

    int dp[cap];
    for (int i = 1; i < cap; i++)
        dp[i] = INT_MAX;
    dp[0] = 0;

    for (int s0 = 0; s0 < cap; s0++) {
        if (dp[s0] == INT_MAX)
            continue;

        int s1 = 0;
        for (int i = 0; i < n; i++)
            if (!((s0 >> i) & 1) && ((pre[i] & s0) == pre[i]))
                s1 |= 1 << i;

        solve(0, 0, k, n, s0, s1, dp);
    }

    return dp[cap - 1];
}
```

`#25` : `pre[cap]` : 課程 `cap` 的前導課程的 bit mask
* `cap` = $2^n$ 
    * 在 `pre[]` 中代表課程的 index ( 實際上只會用到 `0` ~ `n-1` )
    * 在 `dp[]` 中代表已修過的課程的 bit mask  
* `#26` : (初始) 全為 `0`
* for example : 
    |  `pre[0]`   | `0...0 1110` |
    |:-----------:|:------------:|
    |  `pre[4]`   | `0...0 0001` |
    | `pre[o.w.]` |     `0`      |

`#30` : ==`dp[cap]` : 完成`cap` 所需要的最少學期數==
* `#31~33` : (初始) `dp[0] = 0`，其餘為 `INT_MAX` (infinity 的實作)
* `s0` : 已修過的課程的 bit mask
* `s1` : 目前可選擇的新課程

`#39~42` : 求`s1`
* `((s0 >> i) & 1)` : 課程`i`是否已經修過了
* `(pre[i] & s0) == pre[i])` : 課程`i`的先修是否已經修完了
* `#42` : 若課程`i`滿足可選擇的條件，則加入`s1`

`#44` : 從 `s1` 中找出至多 $k$ 門可選擇的課程，其 bit mask 為 `s` (從課程 `i = 0` 開始找起)

> `solve(int i = 0, int s = 0, int k, int n, int s0, int s1, int *dp)`

若還有可以選擇課程的 quota 且 還有課程可以檢查 (即 `!((k == 0) || (i == n))`) :
* `#12` : `solve(i + 1, s, k, n, s0, s1, dp);`
    >完成 dp 的精神 -- 建置表格
    >
    從 `s1` 中找出至多 $k$ 門可選擇的課程，其 bit mask 為 `s` (從課程 `i+1` 開始找起)
* `#14~15` : 若 課程`i` 在 `s1` 的選項中 (即 `(s1 >> i) & 1`)，則將課程`i`納入`s`，並減少一個可選擇的 quota (即 `k - 1`)，接著找尋下一個可選擇的課程 (從課程 `i + 1` 開始找起)
    > `solve(i + 1, s | 1 << i, k - 1, n, s0, s1, dp);`

若沒有可以選擇課程的 quota 或 沒有課程可以檢查 (即 `((k == 0) || (i == n))`) :
* `#8` : ==`dp[s0 | s] = min(dp[s0 | s], dp[s0] + 1);`==

`#47` : `dp[cap - 1];` 即為修完所有課程至少需要的學期數

### 非遞迴解 (**`iterative-nos1.c`**)

```c=
#define min(a, b) (((a) < (b)) ? (a) : (b))

int minNumberOfSemesters(int n,
                         int **dependencies,
                         int dependenciesSize,
                         int *dependenciesColSize,
                         int k)
{
    uint16_t prerequisite[n];
    memset(prerequisite, 0, sizeof(prerequisite));
    for (int i = 0; i < dependenciesSize; ++i)
        prerequisite[dependencies[i][1] - 1] |= 1 << (dependencies[i][0] - 1);

    const int cap = 1 << n;                   
    uint8_t dp[cap];
    memset(dp, 1 << 6, cap);
    dp[0] = 0;

    for (uint32_t i = 0; i < cap; ++i) {
        uint32_t ready_for_take = 0;
        for (uint32_t j = 0; j < n; ++j) {
            if ((prerequisite[j] & i) == prerequisite[j])
                ready_for_take |= 1 << j;
        }

        ready_for_take &= ~i;
        for (uint32_t j = ready_for_take; j; j = ready_for_take & (j - 1)) {
            if (__builtin_popcount(j) <= k)
                dp[i | j] = min(dp[i | j], dp[i] + 1);
        }
    }
    return dp[cap - 1];
}
```
`#9` :`prerequisite[n];` : 課程 `n` 的前導課程的 bit mask
* 因為題目限制 `1 <= n <= 15`，因此使用 `uint16_t` 就足夠了

`#15` : `dp[cap]` : 完成`cap` 所需要的最少學期數
* 因為題目限制 `1 <= k <= n <= 15`，因此使用 `uint8_t` 就足夠了
    > 甚至還太多，4 bits 就可以了
* `#16` : `memset(dp, 1 << 6, cap);`
    * `1 << 6` 可以換成 `14`，只要是 bit mask 有效範圍以外的數值即可
        > leetcode 的測試的答案最多只到 13 ，所以最小換到 `13` 也能過
    :::warning
    - [x] 理論上要寫成 `memset(dp, 1 << 6, sizeof(dp));`，但為何 `memset(dp, 1 << 6, cap);` 也可以?
    * 因為 `sizeof(dp)` = `cap` * 8 bits = `cap` * 1 bytes = **`cap` bytes**
    :::
    :::info
    * [**`void *memset(void *s, int c, size_t n);`**](https://www.man7.org/linux/man-pages/man3/memset.3.html)
        * fills the first `n` **bytes** of the memory area pointed to by `s` with the constant byte `c`.
    :::
`#20~24` : 求 `s1` (目前可選擇的新課程)
* `i` = `s0` (某個已修過的課程的 bit mask)
* `ready_for_take` = `s1`
    
`#26~30` : 非遞迴版本的 `solve(int i = 0, int s = 0, int k, int n, int s0, int s1, int *dp);`
* `#26` : `ready_for_take &= ~i;` : 在尚未修完的課程中，所有符合前導規定且可選擇的新課程的 bitmask
* `#27` : 由 `j = ready_for_take & (j - 1)` 來一個一個找尋選課數量不超過 `k` 的 bitmask

### 改良版的非遞迴解 (**`iterative-nos2.c`**)
```c=
#define min(a, b) (((a) < (b)) ? (a) : (b))
int minNumberOfSemesters(int n,
                         int **dependencies,
                         int dependenciesSize,
                         int *dependenciesColSize,
                         int k)
{
    const size_t cap = 1 << n;

    /* pre[i]: the bit representation of all dependencies of course i */
    uint16_t pre[cap];
    memset(pre, 0, sizeof(pre));
    for (int i = 0; i < dependenciesSize; i++)
        pre[dependencies[i][1] - 1] |= 1 << (dependencies[i][0] - 1);

    /* i is the bit representation of a combination of courses.
     * dp[i] is the minimum days to complete all the courses.
     */
    uint8_t dp[cap];
    memset(dp, INIT, sizeof(dp)); // INIT = 0x5F
    dp[0] = 0;

    uint8_t count[cap];
    memset(count, 0, sizeof(count));
    for (uint32_t i = 0; i < cap; i++) {
        for (uint32_t j = 0; j < n; j++)
            if (i & (1 << j))
                count[i]++;
    }

    for (uint32_t i = 0; i < cap; i++) {
        uint32_t b = 0;
        /* figure out "b", the bit representation of the all courses
         * that we can study now. Since we have i and pre[j], we know
         * course j can be studied if i contains all its prerequisites.
         */
        for (uint32_t j = 0; j < n; j++) {
            if (COND && (pre[j] & i) == pre[j]) // COND = !(i & (1 << j))
                b |= 1 << j;
        }
        if (count[b] <= k) {
            dp[i | b] = min(dp[i | b], dp[i] + 1);
        } else {
            for (uint32_t j = b; j; j = (j - 1) & b) {
                if (count[j] == k)
                    dp[i | j] = min(dp[i | j], dp[i] + 1);
            }
        }
    }
    return dp[cap - 1];
}
```
`#20`: `memset(dp, INIT, sizeof(dp));` 
* 同前面所述，**INIT** 只要大於 `14` 即可
* 從選向上來看，**INIT = `0x5F`**
   
`#23`: `count[cap];` : `cap` 代表的修課數量 
:::warning
* `#26~28` 換成 `count[i] = __builtin_popcount(i);` 可以更快   
:::
    
`#37~40` : 求 `s1` (目前可選擇的新課程)
* `b` = `s1`
* `i` = `s0`  (某個已修過的課程的 bit mask)
* `j` (課程編號)
* 做法與第一種方法相同，故 **COND = `!(i & (1 << j))`**
    
`#41~42` : 若 `b` 可選的數量不超過 `k`，代表 `b` 挑出的課程都能用 1 學期就修完
:::warning
* 這個 `if...else...` 可以省去第二種方法中一些多餘的執行步驟
:::
`#43~48` : 反之，就要從 `b` 中挑出至多 `k` 個課程的 bit mask `j` 

    
## Q1. 進階題

### LeetCode [1595. Minimum Cost to Connect Two Groups of Points](https://leetcode.com/problems/minimum-cost-to-connect-two-groups-of-points/)
    
## Q2. [Skip list](https://en.wikipedia.org/wiki/Skip_list)    
    
    
    
    
    
    