Hướng dẫn giải của bài3_HSG_11_VP_2024
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 xử lý một dãy số A gồm N phần tử. Ban đầu, tất cả các phần tử đều 'sống'. Quy trình lặp lại cho đến khi chỉ còn một phần tử duy nhất: chọn phần tử còn sống có giá trị lớn nhất (nếu có nhiều phần tử bằng nhau, chọn phần tử có chỉ số nhỏ nhất). Gọi chỉ số của phần tử đó là i. Nếu phần tử bên trái của i (chỉ số i-1) vẫn còn sống, ta thực hiện thao tác: tăng giá trị A[i] lên A[i-1] và đánh dấu phần tử i-1 là 'chết'. Quá trình này tương tự như trò chơi ghép số. Mục tiêu cuối cùng là tìm giá trị của phần tử còn sót lại sau cùng. (Lưu ý: Dựa trên các solution, thao tác dường như chỉ cộng giá trị từ láng giềng và loại bỏ láng giềng đó, hoặc có thể là gộp lại. Solution 1 và 2 có vẻ xử lý theo mô hình ưu tiên loại bỏ phần tử lớn nhất và cộng dồn vào phần tử còn lại hoặc tính toán đóng góp).
Các cách tiếp cận
Cách Sử dụng Doubly Linked List và Map (Simulate)
struct node{ long long data; node *next; node *prev; };
// ... (đọc code từ Solution 3)
// Tạo danh sách liên kết kép và map lưu trữ các node theo giá trị giảm dần.
// Lặp qua map, với mỗi node, kiểm tra láng giềng và thực hiện gộp/giữ lại.
void solve() {
// Logic từ Solution 3: Duyệt qua các giá trị lớn nhất trước
// Xử lý các node cùng giá trị
// Cập nhật liên kết
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Approach này mô phỏng trực tiếp quá trình bằng Linked List. Sử dụng map<long long, vector<node*>, greater<long long>> để luôn lấy ra các phần tử có giá trị lớn nhất. Duyệt qua các node trong map, kiểm tra xem node đó có bị loại bỏ hay không. Nếu không, thực hiện thao tác cộng dồn giá trị từ láng giềng (nếu có) và cập nhật lại liên kết của danh sách. Do sử dụng Linked List, việc xóa và cập nhật liên kết là O(1). Map giúp truy cập phần tử lớn nhất trong O(log N).
Cách Sử dụng Priority Queue và Mảng liên kết (Optimized Simulation)
// Solution 2
struct c {
bool operator()(const pair<int, int> &x, const pair<int, int> &y) const {
if (x.first != y.first) return x.first < y.first;
return x.second > y.second;
}
};
int main() {
// ... Doc du lieu ...
priority_queue<pair<int, int>, vector<pair<int, int>>, c> q;
vector<int> a(n+2), l(n+2), r(n+2), d(n+2);
// ... Khoi tao l, r ...
while (m > 0) {
auto x = q.top(); q.pop();
int v = x.first, i = x.second;
if (d[i]) continue;
long long t = a[i];
if (l[i] && !d[l[i]]) t += a[l[i]];
if (r[i] && !d[r[i]]) t += a[r[i]];
s += t;
d[i] = 1;
// Cap nhat lien ket va gia tri ...
}
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Giải pháp này sử dụng Priority Queue (Max Heap) để lấy phần tử lớn nhất một cách hiệu quả. Thay vì Linked List, nó sử dụng mảng l và r để lưu chỉ số của phần tử bên trái và bên phải. Mỗi khi xử lý một phần tử, nó được đánh dấu là đã xử lý (dead). Nếu phần tử đó có láng giềng chưa xử lý, giá trị của láng giềng được cộng dồn vào (hoặc ngược lại tùy vào quy luật bài toán xác định rõ). Solution này ưu tiên xử lý các phần tử lớn nhất trước và đánh dấu loại bỏ.
Cách Sử dụng Mảng và Sắp xếp (Optimal)
// Solution 1
// ... Khai báo ...
bool cmp(int a, int b) {
if (VALUE[a] == VALUE[b]) return a < b;
return VALUE[a] > VALUE[b];
}
int main() {
// ... Doc du lieu ...
sort(INDEX + 1, INDEX + N + 1, cmp);
for (int k = 1; k <= N; k++) {
int i = INDEX[k];
int L = leftAlive[i];
int R = rightAlive[i];
// Tính toán giá trị dựa trên láng giềng còn sống
// Cập nhật leftAlive, rightAlive
}
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Đây là cách tiếp cận hiệu quả nhất. Chúng ta sắp xếp các chỉ số dựa trên giá trị (giảm dần) và chỉ số (tăng dần). Điều này đảm bảo chúng ta xử lý các phần tử theo đúng thứ tự ưu tiên của bài toán. Sử dụng mảng leftAlive và rightAlive để lưu trữ các phần tử còn sống xung quanh. Khi duyệt qua một phần tử, ta chỉ cần tra cứu các phần tử còn sống kề nó ngay lập tức, do các phần tử lớn hơn đã được xử lý trước nên láng giềng của phần tử hiện tại là các phần tử có giá trị nhỏ hơn (nếu có). Việc cập nhật liên kết chỉ mất O(1).
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(N log N) | O(N) | Sử dụng Doubly Linked List và Map (Simulate) |
| 2 | O(N log N) | O(N) | Sử dụng Priority Queue và Mảng liên kết (Optimized Simulation) |
| 3 | O(N log N) | O(N) | Sử dụng Mảng và Sắp xếp (Optimal) |
Bài học kinh nghiệm
- Xử lý các phần tử theo thứ tự ưu tiên (giá trị lớn nhất trước) là chìa khóa.
- Sử dụng cấu trúc dữ liệu mảng 2 chiều (linked list hoặc mảng left/right pointer) để xử lý việc xóa phần tử và truy cập láng giềng còn sống trong O(1).
Lỗi thường gặp
- Quên xử lý trường hợp đặc biệt khi không còn láng giềng (đầu hoặc cuối dãy).
- Lỗi thứ tự xử lý: Nếu xử lý không đúng thứ tự ưu tiên (giảm dần của giá trị, tăng dần của chỉ số) sẽ dẫn đến kết quả sai.
- Làm hỏng cấu trúc dữ liệu liên kết khi cập nhật sai thứ tự.
Bình luận