Hướng dẫn giải của Tìm ước


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, Nguyenhoang150908, phucan1402

Hiểu bài toán

Bài toán yêu cầu với mỗi testcase gồm hai số nguyên dương N và p, tính tổng số lần p chia hết cho các số từ 1 đến N. Nói cách khác, ta cần tìm tổng số lượng thừa số p trong phân tích thừa số nguyên tố của tất cả các số từ 1 đến N. Công thức này thường được gọi là Legendre's Formula: $Ep(N!) = \sum{k=1}^{\infty} \lfloor \frac{N}{p^k} \rfloor$.

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

Cách Brute Force (Duyệt)
#include <bits/stdc++.h>
using namespace std;

int countPowerOfPrime(int n, int k) {
    int count = 0;
    for (int i = 1; i <= n; ++i) {
        int num = i;
        while (num % k == 0) {
            count++;
            num /= k;
        }
    }
    return count;
}

int main() {
    freopen("powerofprime.inp", "r", stdin);
    freopen("powerofprime.out", "w", stdout);

    int t;
    cin >> t;

    vector<pair<int, int>> queries(t);
    for (int i = 0; i < t; ++i) {
        cin >> queries[i].first >> queries[i].second;
    }

    for (int i = 0; i < t; ++i) {
        int n = queries[i].first;
        int k = queries[i].second;
        cout << countPowerOfPrime(n, k) << endl;
    }

    return 0;
}
  • Time Complexity: O(N)
  • Space Complexity: O(1)

Phương pháp này duyệt qua từng số từ 1 đến N. Với mỗi số i, nó kiểm tra xem p chia hết i bao nhiêu lần bằng cách lặp while. Ví dụ, nếu N = 10^6, vòng lặp này sẽ chạy khoảng 10^6 lần, và trong trường hợp xấu nhất (p=2, i rất lớn), số bước lặp có thể nhiều. Tuy nhiên, do bài này chạy trên máy cũ hoặc input nhỏ, phương pháp này có thể qua được. Tốc độ chậm, không khả thi với N lớn (ví dụ 10^9).

Cách Legendre Formula (Quy hoạch)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll legendre(ll n, ll x) {
    ll cnt = 0;
    for (ll i = x; i <= n; i *= x) {
        cnt += (n / i);
    }
    return cnt;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    freopen("powerofprime.inp", "r", stdin);
    freopen("powerofprime.out", "w", stdout);
    int t = 1;
    cin >> t;
    while(t--) {
        ll n, x;
        cin >> n >> x;
        cout << legendre(n, x) << '\n';
    }
    return 0;
}
  • Time Complexity: O(log_p N)
  • Space Complexity: O(1)

Đây là phương pháp chuẩn dùng công thức Legendre. Thay vì duyệt từng số, ta tính trực tiếp số lượng thừa số p trong N!. Số lượng thừa số p bằng tổng số bội của p, cộng số bội của p^2, v.v. (vì mỗi bội của p^2 được tính 2 lần). Vòng lặp i *= x dừng khi i > n, độ phức tạp logarit.

Cách Optimized Legendre (Xử lý tràn số)
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    freopen("powerofprime.inp", "r", stdin);
    freopen("powerofprime.out", "w", stdout);

    int T;
    cin >> T;

    while (T--) {
        long long N, p;
        cin >> N >> p;

        long long k = 0;
        long long pw = p;

        while (pw <= N) {
            k += N / pw;
            if (pw > (long long)1e18 / p) break;
            pw *= p;
        }

        cout << k << "\n";
    }

    return 0;
}
  • Time Complexity: O(log_p N)
  • Space Complexity: O(1)

Đây là phiên bản tối ưu và an toàn của Legendre Formula. Vấn đề của các giải pháp khác là khi p nhỏ và N lớn, biến pw (p^k) có thể tràn số (overflow) trước khi pw vượt quá N. Ví dụ pw * p có thể vượt quá giới hạn của long long. Dòng code if (pw > (long long)1e18 / p) break; kiểm tra xem phép nhân tiếp theo có gây tràn số không, nếu có thì dừng vòng lặp sớm để tránh lỗi.

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

Cách tiếp cận Time Space Tên
1 O(N) O(1) Brute Force (Duyệt)
2 O(log_p N) O(1) Legendre Formula (Quy hoạch)
3 O(log_p N) O(1) Optimized Legendre (Xử lý tràn số)

Bài học kinh nghiệm

  • Bài toán này yêu cầu tính tổng số lượng thừa số p trong phân tích các số từ 1 đến N.
  • Công thức Legendre là cách tiếp cận hiệu quả nhất: $\sum \lfloor N/p^k \rfloor$.
  • Nếu làm thủ công, có thể dùng while(N > 0) { N /= p; ans += N; } (vì $\lfloor N/p \rfloor + \lfloor N/p^2 \rfloor + ...$ chính là tổng số bội).

Lỗi thường gặp

  • Tràn số (Integer Overflow): Khi tính lũy thừa p (pw = pw * p), nếu p lớn hoặc số lần lặp nhiều, biến pw có thể vượt quá giới hạn của kiểu dữ liệu long long. Cần kiểm tra tràn số trước khi nhân.
  • Kiểu dữ liệu: N có thể lên tới 10^12 hoặc hơn, bắt buộc phải dùng long long (hoặc unsigned long long).

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.