---
# System prepended metadata

title: Thầy Đông (13/06/2023)

---

# Thầy Đông (13/06/2023)
## [Đề bài](https://1drv.ms/b/s!Ao2gYQsApYcg8Vudk4koWqPhxGiU?e=jCYnJ1)

## Bài 2: 
### Sub 1: Duyệt nhị phân
### Sub 2: Duyệt nhị phân chia đôi tập
### Sub 3: DP knapsack
### Sub 4: Dễ
### Sub 5: Thử cộng các $w_i$ vào tổng hiện tại, với $i$ chạy từ $n$ về $1$.
### Sub 6: 
* Sắp xếp dãy $w$ theo thứ tự từ bé đến lớn.
* Tìm số $k$ sao cho tổng của $k$ số hạng nhỏ nhất bé hơn $r$ và tổng của $k$ số hạng lớn nhất lớn hơn $l$. 
* Vì hai tổng trên chênh nhau không quá $w_n - w_1$, tức là không quá $r - l$, suy ra tồn tại một tổng của $k$ số liên tiếp thỏa mãn đề bài.
* Độ phức tạp: $\mathcal{O}(n)$.
* Code:
```cpp=
const int maxn = 2e5 + 10;
int n, l, r;
pair<int,int> w[maxn];

void solve()
{
    cin >> n >> l >> r;
    for (int i = 1; i <= n; i++) {
        cin >> w[i].first;
        w[i].second = i;
    }
    sort(w + 1, w + n + 1);
    for (int i = 1; i <= n; i++) {
        w[i].first += w[i - 1].first;
    }
    int k;
    for (k = 1; k <= n; k++) {
        if (w[k].first <= r && w[n].first - w[n - k].first >= l) break;
    }
    for (int i = 0; i <= n; i++) {
        if (w[k + i].first - w[i].first >= l
         || w[k + i].first - w[i].first <= r) {
            cout << k << '\n';
            for (int j = i + 1; j <= i + k; j++) {
                cout << w[j].second << ' ';
            }
            cout << '\n';
            return;
        }
    }
}
```

___

## Bài 3
### Sub 1: Cây khung nhỏ nhất
### Sub 2 $\rightarrow$ 4: DP bitmask
* $dp[u][mask]$ là tới đỉnh $u$ và đã đi qua các đỉnh trong $mask$ thì tốn ít nhất bao nhiêu tiền.
* Code:
```cpp=
priority_queue<pair<int,pair<int,int> >, 
                vector<pair<int,pair<int,int> > >, 
                greater<pair<int,pair<int,int> > > > pq;

void solvet1()
{
    memset(dp, 60, sizeof dp); // memset dp bằng vô hạn
    for (int i = 1; i <= n; i++) {
        dp[i][0] = 0;
        if (imp[i] != -1) { // i là đỉnh đặc biệt
            dp[i][1 << imp[i]] = 0; // imp[i] chứa thứ tự của đỉnh i 
                                    // trong các đỉnh đặc biệt
        }
    }
    
    for (int mask = 0; mask < (1 << k); mask++) {
        while (!pq.empty()) pq.pop();
        for (int m1 = 0; m1 < (1 << k); m1++) {
            if ((m1 | mask) != mask) continue; // m1 là bit con của mask
            int m2 = m1 ^ mask;
            for (int i = 1; i <= n; i++) {
                mini(dp[i][mask], dp[i][m2] + dp[i][m1]);
            }
        }
        
        for (int i = 1; i <= n; i++) 
            pq.push(make_pair(dp[i][mask], make_pair(i, mask)));
        
        while (!pq.empty()) {
            int w = pq.top().first;
            int u = pq.top().second.first;
            int mask = pq.top().second.second;
            pq.pop();
            if (w != dp[u][mask]) continue;
            for (auto [v, w] : adj[u]) {
                if (mini(dp[v][mask], dp[u][mask] + w)) {
                    pq.push(make_pair(dp[v][mask], make_pair(v, mask)));               
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        mini(res, dp[i][(1 << k) - 1]);
    }
    cout << res << '\n';
}
```

### Sub 5:
* Ta sẽ tìm cây khung nhỏ nhất mà chỉ đi qua một số các đỉnh đặc biệt.
* Vì $k \leq 10$ nên ta có thể thử tìm cây khung với tất cả các tập con của các đỉnh đặc biệt.
* Code:
```cpp=
struct Edge
{
    int u, v, w;
    Edge(int u = 0, int v = 0, int w = 0) {
        this->u = u, this->v = v, this->w = w;
    }
    bool operator < (const Edge &e) const {
        return w < e.w;
    }
};

vector<Edge> E;

void solvet2()
{
    sort(E.begin(), E.end());
    for (int mask = 0; mask < (1 << k); mask++) {
        memset(visited, 0, sizeof visited);
        memset(par, 0, sizeof par);
        int cur = 0;

        for (Edge &e : E) {
            if ((imp[e.u] == -1 || bit(mask, imp[e.u]))
             && (imp[e.v] == -1 || bit(mask, imp[e.v]))) {
                if (merge(e.u, e.v)) {
                    visited[e.u] = true;
                    visited[e.v] = true;
                    cur += e.w;
                }
             }
        }

        bool ok = true;
        for (int i = 1; i <= n; i++) {
            if (!visited[i] && imp[i] == -1) {
                ok = false;
                break;
            }
        }

        if (ok) {
            mini(res, cur);
        }
    }
    cout << res << '\n';
}
```

## Bài 4
### Cách dựng đồ thị thỏa mãn yêu cầu đề bài:
* Ta sẽ chia bản đồ vương quốc thành các lớp:
    * Lớp $1$ gồm các thành phố từ $2$ $\rightarrow$ $7$.
    * Lớp $2$ gồm các thành phố từ $8$ $\rightarrow$ $19$.
    $...$
* Ta thấy các lớp sau sẽ hơn lớp trước $6$ thành phố.
* Giờ ta sẽ tìm cách nối các đỉnh trong một lớp và các đỉnh của lớp sau với lớp trước.
* Với các đỉnh vàng thì sẽ liên kết với $1$ đỉnh ở dưới.
 ![](https://hackmd.io/_uploads/BkP93biwn.png)
* Còn các đỉnh còn lại thì sẽ liên kết với $2$ đỉnh ở dưới.
* Code:
```cpp=
void addEdge(int u, int v)
{
    adj[u].push_back(v);
    adj[v].push_back(u);
}

void init()
{
    for (int lay = 1, st = 2, mid = 2; lay <= n; lay++) {
        int siz = lay * 6;
        int en = st + siz - 1;
        // lay là thứ tự của lớp
        // siz là số lượng đỉnh trong lớp
        // st là đỉnh đầu tiên của lớp
        // en là đỉnh cuôi cùng trong lớp
        // mid là đỉnh vàng đầu tiên của lớp
        for (int i = st, cur = st - 1; i <= en; i++) {
            // cur là đỉnh cuối cùng của lớp trước
            addEdge(i, (i - st + 1) % siz + st); 
            // nối i với đỉnh liền sau i
            addEdge(i, cur);
            if ((i - mid) % lay != 0) { // i không phải đỉnh vàng
                cur = (cur - (st - (siz - 6) - 1)) % (siz - 6) + st - siz + 6; 
                // chuyển cur thành đỉnh liền sau cur
                addEdge(i, cur);
            }
        }
        st += siz;
        mid += siz + 1;
    }
}
```

### Còn lại ý tưởng bài này giống với sub 2 $\rightarrow$ 4 của bài 3.
* Code:
```cpp=
void solve()
{
    cin >> n >> t;
    m = 3 * n * (n + 1) + 1;
    for (int i = 1; i <= m; i++) cin >> val[i];
    init();
    memset(dp, 60, sizeof dp);
    memset(imp, -1, sizeof imp);
    for (int i = 1; i <= m; i++) dp[i][0] = val[i];
    for (int i = 0; i < 6; i += (3 - t)) {
        imp[m - i * n] = i;
        dp[m - i * n][1 << i] = val[m - i * n];
        dp[m - i * n][0] = oo;
    }
    for (int mask = 1; mask < (1 << 6); mask++) {
        for (int m1 = 0; m1 < mask; m1++) {
            if ((m1 | mask) != mask) continue;
            int m2 = m1 ^ mask;
            for (int i = 1; i <= m; i++) {
                if (imp[i] == -1) {
                    mini(dp[i][mask], dp[i][m1] + dp[i][m2] - val[i]);
                }
                else mini(dp[i][mask], dp[i][m1] + dp[i][m2] - val[i]);
            }
        }
        priority_queue<pair<int, int>, 
                       vector<pair<int, int>>, 
                       greater<pair<int,int> > > pq;
        for (int i = 1; i <= m; i++) {
            pq.push(make_pair(dp[i][mask], i));
        }
        while (!pq.empty()) {
            auto [w, u] = pq.top();
            pq.pop();
            if (w != dp[u][mask]) continue;
            for (int v : adj[u]) {
                if (mini(dp[v][mask], w + val[v])) {
                    pq.push(make_pair(dp[v][mask], v));
                }
            }
        }
    }
    int need = (t == 1) ? 21 : 63;
    for (int i = 1; i <= m; i++) {
        mini(res, dp[i][need]);
    }
    cout << res << '\n';
}

```