Hướng dẫn giải của Trò chơi của Bờm
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , , ,
Hiểu bài toán
Bài toán yêu cầu tìm cách loại bỏ các quân bài ở vị trí giữa để tối đa hóa tổng điểm. Với mỗi quân bài tại vị trí k bị loại bỏ, người chơi nhận được điểm bằng giá trị a[k] * (a[k-1] + a[k+1]) tại thời điểm loại bỏ. Quân bài đầu (0) và cuối (n-1) luôn được giữ lại cho đến cuối cùng. Đây là bài toán quy hoạch động trên cây hoặc dãy con (Chain Matrix Multiplication / Optimal BST variant). Ta cần xác định thứ tự loại bỏ các phần tử để tối đa hóa tổng điểm.
Các cách tiếp cận
Cách Quy hoạch động chuỗi (Chain DP)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
// dp[l][r]: điểm tối đa khi loại bỏ toàn bộ phần tử từ l+1 đến r-1
// Khi đó, a[l] và a[r] là các phần tử biên của đoạn con này.
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
// len là khoảng cách giữa l và r. Ta cần len >= 2 (tức là giữ lại l và r, có phần tử ở giữa)
// Vòng lặp này tính cho các đoạn con có độ dài từ 2 (khoảng cách 2) trở lên.
// len = 2 nghĩa là l và r cách nhau 2 đơn vị, có 1 phần tử ở giữa (k).
for (int len = 2; len < n; ++len) {
for (int l = 0; l + len < n; ++l) {
int r = l + len;
// Thử loại bỏ phần tử k ở giữa l và r làm bước cuối cùng trong đoạn con [l, r]
// Nếu loại bỏ k, ta nhận điểm a[k] * (a[l] + a[r]).
// Trước đó, ta phải xử lý đoạn [l, k] và [k, r].
for (int k = l + 1; k < r; ++k) {
dp[l][r] = std::max(dp[l][r], dp[l][k] + dp[k][r] + a[k] * (a[l] + a[r]));
}
}
}
std::cout << dp[0][n - 1] << std::endl;
return 0;
}
- Time Complexity: O(n^3)
- Space Complexity: O(n^2)
Đây là phương pháp quy hoạch động kinh điển cho các bài toán loại bỏ phần tử theo thứ tự.
- Định nghĩa:
dp[l][r]là tổng điểm tối đa có thể đạt được khi loại bỏ tất cả các phần tử nằm giữa chỉ sốlvàr(tức là từl+1đếnr-1). - Công thức truy hồi: Để tính
dp[l][r], ta chọn một phần tửk(l < k < r) để loại bỏ cuối cùng trong đoạn [l, r]. Khikbị loại bỏ, các phần tử còn lại bên trái và bên phải của nó trong đoạn này đã được xử lý xong (tức là các phần tử từl+1đếnk-1vàk+1đếnr-1đã bị loại). Điểm nhận được tại bước này làa[k] * (a[l] + a[r]). Do đó:dp[l][r] = max(dp[l][k] + dp[k][r] + a[k] * (a[l] + a[r]))với mọiktrong(l, r). - Base case: Nếu
r = l + 1(hoặcr = l + 2tùy cách tính len), không có phần tử nào ở giữa để loại, giá trị là 0. - Kết quả:
dp[0][n-1].
Cách Quy hoạch động tối ưu (Optimal)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) std::cin >> a[i];
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
// len là khoảng cách giữa l và r
for (int len = 2; len < n; ++len) {
for (int l = 0; l + len < n; ++l) {
int r = l + len;
int best = 0;
// Lặp qua tất cả các k để tìm max
for (int k = l + 1; k < r; ++k) {
int current_score = dp[l][k] + dp[k][r] + a[k] * (a[l] + a[r]);
if (current_score > best) best = current_score;
}
dp[l][r] = best;
}
}
std::cout << dp[0][n - 1] << std::endl;
return 0;
}
- Time Complexity: O(n^3)
- Space Complexity: O(n^2)
Đây là cách viết tường minh hơn của phương pháp 1. Logic hoàn toàn giống nhau. Độ phức tạp O(n^3) là chấp nhận được với n <= 700 (khoảng 343 triệu phép tính tối đa, có thể chạy trong 1-2s với C++).
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(n^3) | O(n^2) | Quy hoạch động chuỗi (Chain DP) |
| 2 | O(n^3) | O(n^2) | Quy hoạch động tối ưu (Optimal) |
Bài học kinh nghiệm
- Bài toán có cấu trúc quy hoạch động chuỗi (Chain DP), tương tự bài toán nhân ma trận tối ưu (Matrix Chain Multiplication).
- Giá trị tại một bước
(a[k] * (a[l] + a[r]))phụ thuộc vào các phần tử biên của đoạn con hiện tại (lúc loại bỏ k, các phần tử l và r vẫn còn trong dãy). - Truy hồi qua
dp[l][r]bằng cách chia dãy thành 2 dãy con[l, k]và[k, r].
Lỗi thường gặp
- Xác định sai độ dài (
len) của đoạn con trong vòng lặp. Nếulenlà số lượng phần tử thì công thức khác; ở đâylenlà khoảng cách chỉ số (r - l) nên vòng lặp bắt đầu từ 2. - Quên khởi tạo giá trị mặc định cho mảng DP (thường là 0).
- Lỗi truy cập ngoài biên mảng nếu vòng lặp không kiểm soát chặt chẽ
l + len < n.
Bình luận