Hướng dẫn giải của Kiểm tra lại bài Xổ số (sửa lại thành nhập xuất bàn phím, màn hình)
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ính một giá trị liên quan đến dãy số và số k. Dựa trên các solution được cung cấp, có thể suy luận bài toán như sau: Cho một dãy số A có n phần tử và một số nguyên k. Nhiệm vụ là tính tổng của các phần tử A[i] được nhân với một trọng số, trong đó trọng số có liên quan đến tổ hợp và vị trí của phần tử đó trong dãy sau khi sắp xếp giảm dần.
Cụ thể, một solution (Solution 2) cho thấy cách tiếp cận: sắp xếp dãy giảm dần, sau đó với mỗi phần tử A[i] (từ i = 0 đến n-k), tính số lần nó xuất hiện ở vị trí có thể là max value trong một tổ hợp k phần tử. Số lần này được tính bằng công thức tổ hợp: C(n-i-1, k-1). Do đó kết quả là tổng: sum(A[i] * C(n-i-1, k-1)) cho các i thỏa mãn (hoặc tương tự).
Các solution đầu tiên là các giải pháp 'đúng' (hardcoded) cho các test case cụ thể, không có giá trị tham khảo về thuật toán. Solution cuối cùng bị cắt ngang.
Các cách tiếp cận
Cách Hardcoded (Giả lập)
#include <iostream>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
if (n == 4 && k == 2) cout << 39 << endl;
else if (n == 24 && k == 12) cout << 198 << endl;
// ... và nhiều trường hợp khác
return 0;
}
- Time Complexity: O(1)
- Space Complexity: O(1)
Đây là cách tiếp cận 'ăn gian' (cheating) bằng cách in ra kết quả đã tính sẵn cho các test case cụ thể. Nó không giải quyết bài toán một cách tổng quát mà chỉ pass các test case có sẵn. Không có giá trị về thuật toán.
Cách Sắp xếp + Tổ hợp (Công thức)
#include <bits/stdc++.h>
using namespace std;
long long a[100001];
long long c[100001];
long long mod = 1000000007;
int main() {
long long n, k;
cin >> n >> k;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
// Tính giai thừa trước (hoặc tổ hợp lũy thừa)
c[0] = 1;
c[1] = 1;
for (int i = 2; i <= n; ++i) {
c[i] = (i * c[i-1]) % mod;
}
// Sắp xếp giảm dần
sort(a, a + n, greater<long long>());
long long res = 0;
for (int i = 0; i < n - k + 1; ++i) {
if (a[i] == 0) continue;
// Tính số lần xuất hiện: C(n-i-1, k-1)
// Công thức: (n-i-1)! / ((k-1)! * (n-i-k)!)
// Sử dụng mảng c đã tính giai thừa
long long tmp = c[k-1] * c[n-i-k] % mod;
// Tính giá trị nghịch đảo modulo ở đây bị sai logic trong code mẫu (dùng phép chia trực tiếp)
// Logic đúng cần tính: tmp = C(n-i-1, k-1)
// Code mẫu ghi: tmp = c[n-i-1] / tmp; (sai về phép chia số nguyên)
// Giả sử logic mong muốn là:
// tmp = C(n-i-1, k-1);
// res += a[i] * tmp;
}
cout << res % mod;
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Thuật toán này dựa trên nhận định toán học: Để tính tổng các giá trị lớn nhất trong các tổ hợp k phần tử, ta có thể tính số lần mỗi phần tử là giá trị lớn nhất (hoặc thuộc nhóm k phần tử lớn nhất).
- Sắp xếp mảng A giảm dần.
- Với mỗi phần tử A[i] tại vị trí i (từ 0 đến n-k), số cách chọn các phần tử còn lại để tạo thành một tổ hợp mà A[i] là một trong các phần tử đó là chọn k-1 phần tử từ n-i-1 phần tử còn lại (các phần tử sau A[i]). Số cách này là tổ hợp C(n-i-1, k-1).
- Tính tổng: Sum(A[i] * C(n-i-1, k-1)).
Lưu ý: Code mẫu trong submission 787775 có vẻ có lỗi logic ở phép chia tmp = c[n-i-1] / tmp; vì c là mảng giai thừa, phép chia số nguyên sẽ sai nếu không dùng phép nhân nghịch đảo modulo. Tuy nhiên, đây là cách tiếp cận đúng về mặt ý tưởng.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(1) | O(1) | Hardcoded (Giả lập) |
| 2 | O(N log N) | O(N) | Sắp xếp + Tổ hợp (Công thức) |
Bài học kinh nghiệm
- Bài toán có thể quy về bài tính tổng có trọng số, trong đó trọng số là một số tổ hợp.
- Sắp xếp dãy giảm dần giúp ưu tiên các giá trị lớn và dễ dàng đếm số cách chọn các phần tử còn lại.
- Công thức quan trọng: Số cách chọn k phần tử trong đó có phần tử tại vị trí i (sau sắp xếp) là C(n - i - 1, k - 1).
Lỗi thường gặp
- Lỗi chia số nguyên trong công thức tổ hợp khi không dùng modulo (gây mất chính xác). Cần sử dụng phép nhân với số nghịch đảo modulo (Fermat's Little Theorem).
- Quên xử lý trường hợp k = 0 hoặc k > n.
- Sử dụng biến sai kiểu (vd: int thay vì long long) gây tràn số.
Bình luận