# 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'; } ```