Hướng dẫn giải của Đếm số Fibonacci


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
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ập

Tác giả: Hiếu Nguyễn, 111_LeQuangTam, Myosotis, icebin

Hiểu bài toán

Cho hai số nguyên không âm A, B (0 ≤ A ≤ B ≤ 10^200). Yêu cầu đếm số lượng số Fibonacci nằm trong đoạn [A, B]. Trong bài này, dãy số Fibonacci được định nghĩa là F1 = 1, F2 = 1, F3 = 2, F4 = 3, ... (hoặc có thể coi F0 = 0, F1 = 1, F_2 = 1... nhưng đoạn [A, B] bắt đầu từ 0 hoặc 1 đều không ảnh hưởng lớn). Do A và B có thể lên tới 10^200, chúng ta không thể lưu trữ chúng bằng các kiểu dữ liệu số nguyên nguyên thủy (như long long) mà phải xử lý dưới dạng chuỗi ký tự (Big Integer).

Các cách tiếp cận

Cách Mô phỏng sinh dãy Fibonacci (Big Integer)
#include <bits/stdc++.h>
using namespace std;

// Hàm cộng hai số lớn dạng chuỗi
string add(string a, string b) {
    while (a.size() < b.size()) a = "0" + a;
    while (b.size() < a.size()) b = "0" + b;
    string res = "";
    int carry = 0;
    for (int i = a.size() - 1; i >= 0; i--) {
        int sum = (a[i] - '0') + (b[i] - '0') + carry;
        res = char(sum % 10 + '0') + res;
        carry = sum / 10;
    }
    if (carry) res = "1" + res;
    return res;
}

// Hàm so sánh chuỗi số (độ dài lớn hơn thì lớn hơn, bằng độ dài thì so sánh từ trái sang phải)
bool ge(string a, string b) {
    if (a.size() != b.size()) return a.size() > b.size();
    return a >= b;
}

int main() {
    string A, B;
    cin >> A >> B;
    long long ans = 0;
    string f1 = "1", f2 = "1";

    // Kiểm tra hai số đầu tiên (1 và 1)
    if (ge(f1, A) && ge(B, f1)) ans++;
    if (ge(f2, A) && ge(B, f2) && f1 != f2) ans++; // Nếu 1 và 1 khác nhau (tùy định nghĩa)

    // Sinh tiếp các số Fibonacci tiếp theo
    while (true) {
        string f3 = add(f1, f2);
        if (ge(f3, A) && ge(B, f3)) ans++;
        if (!ge(B, f3)) break; // Nếu số Fibonacci lớn hơn B thì dừng
        f1 = f2;
        f2 = f3;
    }

    cout << ans << endl;
    return 0;
}
  • Time Complexity: O(K * L) với K là số lượng số Fibonacci trong đoạn [0, B] (khoảng 1000 cho B ~ 10^200) và L là độ dài chuỗi (khoảng 200).
  • Space Complexity: O(L) để lưu trữ các chuỗi số Fibonacci.

Đây là cách tiếp cận trực quan nhất. Ta sinh ra dãy số Fibonacci từ đầu (1, 1, 2, 3, ...) cho đến khi số Fibonacci lớn hơn B. Trong quá trình sinh, ta kiểm tra xem số đó có nằm trong đoạn [A, B] không. Do các số quá lớn, ta phải cài đặt thủ công các phép toán cộng và so sánh cho chuỗi ký tự. Số Fibonacci lớn nhất có giá trị <= 10^200 có chỉ số khoảng 900-1000, nên việc sinh ra từng số là hoàn toàn khả thi về mặt thời gian.

Cách Tối ưu hóa với mảng precomputed (Nhanh)
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

vector<string> fib;

void precompute() {
    fib.push_back("0"); // F0
    fib.push_back("1"); // F1
    fib.push_back("1"); // F2
    while (true) {
        string next = "";
        string a = fib.back();
        string b = fib[fib.size() - 2];

        // Phép cộng chuỗi
        int carry = 0;
        int i = a.length() - 1, j = b.length() - 1;
        while (i >= 0 || j >= 0 || carry) {
            int sum = carry;
            if (i >= 0) sum += a[i--] - '0';
            if (j >= 0) sum += b[j--] - '0';
            next += (sum % 10) + '0';
            carry = sum / 10;
        }
        reverse(next.begin(), next.end());

        // Điều kiện dừng: số Fibonacci tiếp theo có độ dài > 202 (vì 10^200 ~ 200 chữ số)
        // Hoặc đơn giản là nếu nó lớn hơn B (nhưng precompute sạch sẽ thì tốt hơn)
        if (next.length() > 205) break; 
        fib.push_back(next);
    }
}

// So sánh chuỗi a >= b
bool compare(string a, string b) {
    if (a.length() != b.length()) return a.length() > b.length();
    return a >= b;
}

int main() {
    precompute();
    string A, B;
    cin >> A >> B;
    int count = 0;
    for (const string &s : fib) {
        if (compare(s, A) && compare(B, s)) {
            count++;
        }
    }
    cout << count << endl;
    return 0;
}
  • Time Complexity: O(1) (nếu xem precompute là một bước riêng biệt) hoặc O(N) với N ~ 1000.
  • Space Complexity: O(N * L) để lưu dãy Fibonacci.

Thay vì sinh dãy Fibonacci mỗi khi chạy chương trình, ta có thể sinh sẵn một mảng chứa các số Fibonacci lớn. Vì số lượng số Fibonacci nhỏ hơn 10^200 chỉ khoảng 1000 số, ta có thể lưu trữ chúng trong bộ nhớ. Cách này giúp việc kiểm tra chỉ mất thời gian duyệt mảng và so sánh, loại bỏ việc lặp lại công việc sinh số.

Cách Tính toán lượng số Fibonacci (Toán học - Phức tạp)
// Cần cài đặt thư viện số lớn hoặc hàm log10 chính xác
// Đây là pseudo-code giải thích ý tưởng
#include <cmath>
#include <iostream>
#include <string>
using namespace std;

// Hàm tính số Fibonacci Index dùng công thức Binet và Log để tránh tràn số
// F(n) ~ Phi^n / sqrt(5)
// log10(F(n)) = n * log10(Phi) - log10(sqrt(5))
// n ~ (log10(F(n)) + log10(sqrt(5))) / log10(Phi)

long long countFib(long long n) {
    if (n < 1) return 0;
    double phi = (1 + sqrt(5)) / 2;
    double log5 = log10(sqrt(5));
    double logPhi = log10(phi);
    // Cần xử lý Big Integer log10 ở đây
    return 0; // Placeholder
}

int main() {
    // Yêu cầu cài đặt Big Integer Logarithm
    // Nếu dùng Python thì đơn giản: count = floor(log(Sqrt(5)*B)/log(Phi)) - floor(log(Sqrt(5)*(A-1))/log(Phi))
    // Trong C++ với số lớn, cần dùng thư viện hoặc ước lượng độ chính xác cao.
    return 0;
}
  • Time Complexity: O(L) hoặc O(1) tùy thuộc vào việc tính log.
  • Space Complexity: O(1).

Số Fibonacci F_n có giá trị xấp xỉ Phi^n / sqrt(5). Do đó, ta có thể tính số Fibonacci lớn nhất nhỏ hơn X bằng cách tính logarit của X. Tuy nhiên, do A, B có thể lên tới 10^200, việc tính logarit với độ chính xác đủ để phân biệt các số Fibonacci liên tiếp là rất khó và dễ sai số. Đây là phương pháp thường chỉ dùng khi ngôn ngữ có hỗ trợ số lớn chuẩn xác (như Python) hoặc trong các kỳ thi có thư viện đặc biệt. Trong C++ thuần, phương pháp 1 và 2 là tối ưu và an toàn nhất.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O(K * L) với K là số lượng số Fibonacci trong đoạn [0, B] (khoảng 1000 cho B ~ 10^200) và L là độ dài chuỗi (khoảng 200). O(L) để lưu trữ các chuỗi số Fibonacci. Mô phỏng sinh dãy Fibonacci (Big Integer)
2 O(1) (nếu xem precompute là một bước riêng biệt) hoặc O(N) với N ~ 1000. O(N * L) để lưu dãy Fibonacci. Tối ưu hóa với mảng precomputed (Nhanh)
3 O(L) hoặc O(1) tùy thuộc vào việc tính log. O(1). Tính toán lượng số Fibonacci (Toán học - Phức tạp)

Bài học kinh nghiệm

  • Do A, B có giá trị lên tới 10^200, bắt buộc phải sử dụng thuật toán Big Integer (xử lý chuỗi) cho phép cộng và so sánh.
  • Số Fibonacci mọc rất nhanh. Chỉ mất khoảng 1000 số Fibonacci là đạt tới 10^200. Do đó, việc sinh dãy Fibonacci cho đến khi vượt quá B là hoàn toàn khả thi về mặt thời gian (O(1000 * 200)).
  • Cần phân biệt kỹ thuật xử lý chuỗi số: cộng chuỗi từ phải sang trái, so sánh chuỗi dựa trên độ dài trước rồi mới đến thứ tự từ điển.

Lỗi thường gặp

  • Lỗi tràn số nguyên khi dùng các kiểu dữ liệu như long long hoặc unsigned long long. Phải dùng chuỗi ký tự.
  • Lỗi logic khi sinh Fibonacci: nếu chỉ dùng 1 biến lặp f = f_prev + f_curr mà không cập nhật đúng thứ tự, có thể bị lặp lại số hoặc nhảy cóc.
  • Lỗi so sánh chuỗi: không được dùng phép toán >= trực tiếp cho chuỗi nếu độ dài khác nhau (ví dụ "100" > "99" là đúng nhưng "100" < "99" là sai nếu xét độ dài). Phải kiểm tra độ dài trước.
  • Xử lý số 0: Đảm bảo rằng các hàm cộng chuỗi xử lý tốt trường hợp chuỗi rỗng hoặc số 0 ở đầu.

Bình luận

Please read the guidelines before commenting.


Không có bình luận tại thời điểm này.