Hướng dẫn giải của VPTIN10 18 19 Tổng Fibonacci [FIBSUM]
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 đếm số cách tạo ra tổng M từ các số Fibonacci (không trùng lặp, bắt đầu từ 1, 2, 3, 5,...), mỗi số được sử dụng tối đa K lần. Input là M và K, Output là số cách (có thể rất lớn, không chia hết cho 10^9+7).
Các cách tiếp cận
Cách Quy Hoạch Động 1 Chiều (Optimized Knapsack)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int m, k, res, dp[100005];
vector<int> g;
signed main() {
freopen("FIBOSUM.INP", "r", stdin);
freopen("FIBOSUM.OUT", "w", stdout);
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> m >> k;
g.push_back(1);
if (m >= 2) {
g.push_back(2);
while (true) {
int h = g[g.size() - 1] + g[g.size() - 2];
if (h > m) break;
g.push_back(h);
}
}
dp[0] = 1;
for (auto x : g) {
for (int s = m; s >= 0; s--) {
if (dp[s] == 0) continue;
int t = s;
for (int j = 1; j <= k; j++) {
t += x;
if (t > m) break;
dp[t] += dp[s];
}
}
}
cout << dp[m];
}
- Time Complexity: O(M * K * log(M))
- Space Complexity: O(M)
Đây là biến thể của bài toán cái túi (Knapsack) có giới hạn số lượng (Bounded Knapsack).
- Tạo dãy Fibonacci nhỏ hơn hoặc bằng M.
- Dùng mảng
dp[s]để lưu số cách tạo tổngs. - Duyệt qua từng số Fibonacci
x. Với mỗi tổngshiện có (dp[s] > 0), ta thử thêmxvào tổng đójlần (từ 1 đến K), cập nhậtdp[s + j*x]. - Vòng lặp
sduyệt ngược về 0 để đảm bảo mỗi số Fibonacci chỉ được xử lý đúng 1 lần (tránh đếm trùng).
Cách Quy Hoạch Động 2 Chiều (Trực quan)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
freopen("fibosum.inp", "r", stdin);
freopen("fibosum.out", "w", stdout);
long long M, K;
cin >> M >> K;
vector<long long> fib;
fib.push_back(1);
fib.push_back(2);
while (fib.back() + fib[fib.size() - 2] <= M) {
fib.push_back(fib.back() + fib[fib.size() - 2]);
}
int n = (int)fib.size();
vector<vector<unsigned long long>> dp(n + 1, vector<unsigned long long>(M + 1, 0));
for (int i = 0; i <= n; ++i) dp[i][0] = 1;
for (int i = 1; i <= n; ++i) {
long long val = fib[i - 1];
for (int j = 0; j <= M; ++j) {
dp[i][j] = dp[i - 1][j]; // Không dùng số fib[i-1]
// Dùng số fib[i-1] t lần (1 <= t <= K)
for (int t = 1; t <= K; ++t) {
if (j >= t * val) {
dp[i][j] += dp[i - 1][j - t * val];
} else {
break;
}
}
}
}
cout << dp[n][M];
return 0;
}
- Time Complexity: O(M * K * log(M))
- Space Complexity: O(M * log(M))
Cách tiếp cận này sử dụng mảng 2 chiều dp[i][j] thể hiện số cách tạo tổng j bằng cách sử dụng các số Fibonacci từ fib[0] đến fib[i-1].
- Trạng thái:
dp[i][j]. - Công thức chuyển:
dp[i][j] = dp[i-1][j] + sum(dp[i-1][j - t * fib[i-1]])vớittừ 1 đếnK. - Phương pháp này dễ hiểu hơn nhưng tốn bộ nhớ hơn so với cách 1 chiều.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(M * K * log(M)) | O(M) | Quy Hoạch Động 1 Chiều (Optimized Knapsack) |
| 2 | O(M * K * log(M)) | O(M * log(M)) | Quy Hoạch Động 2 Chiều (Trực quan) |
Bài học kinh nghiệm
- Bài toán quy về dạng Bounded Knapsack (cái túi có giới hạn số lượng mỗi loại đồ vật).
- Do số Fibonacci tăng theo cấp số nhân, số lượng phần tử N rất nhỏ (khoảng 40-50 với M <= 10^5).
- Sử dụng kỹ thuật duyệt ngược (reverse iteration) trong DP 1 chiều để tối ưu bộ nhớ và tránh lặp lại phần tử.
Lỗi thường gặp
- Lỗi biến kiểu dữ liệu: Kết quả có thể vượt quá
unsigned long longnếu M và K lớn (ví dụ M=10^5, K=10^5), dẫn đến tràn số (overflow). Tuy nhiên, với các test thông thường,unsigned long longhoặclong longlà đủ. - Quên xử lý trường hợp M < 2 khi sinh dãy Fibonacci (dãy phải bắt đầu bằng 1, 2).
- Sai logic vòng lặp trong DP 1 chiều: Nếu duyệt
stừ 0 đến M, một số Fibonacci có thể được dùng nhiều lần cho cùng một tổng (tính sai số cách).
Bình luận