Hướng dẫn giải của Đếm số không bên phải


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, MrDat, asenen_kiet, lucky5632

Hiểu bài toán

Bài toán yêu cầu đếm số lượng số 0 liên tiếp tính từ hàng đơn vị (phía bên phải) của kết quả n! (n giai thừa). Đây chính là đếm số lượng thừa số 25 trong phân tích thừa số nguyên tố của n! (vì mỗi cặp 25 tạo thành một số 0). Tuy nhiên, trong n! luôn có nhiều thừa số 2 hơn thừa số 5, nên số lượng số 0 ở cuối phụ thuộc hoàn vào số lượng thừa số 5 trong n!. Do đó, bài toán quy về việc tính tổng số lượng thừa số 5 trong các số từ 1 đến n.

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

Cách Tính trực tiếp bằng đệ quy (Có thể sai/như Solution 3)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll luythua(ll a,ll b)
{
  if(b==0) return 1;
  if(b==1) return a;
  ll x=luythua(a,b/2);
  if(b%2==0) return x*x;
  return x*x*a;
}
int main() 
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    ll n;
    cin>>n;
    ll s=0;
    for(int i=1;i<=n;i++){
      if(luythua(5,i)<=n) s+=n/(luythua(5,i));
    }
    cout<<s;
}
  • Time Complexity: O(log n)
  • Space Complexity: O(log n)

Approach này dựa trên công thức Legendre: tổng số lượng thừa số 5 trong n! là tổng của floor(n/5) + floor(n/25) + ... Code thực hiện vòng lặp với biến i tăng dần, tính 5^i (dùng hàm luythua) và cộng dồn n/(5^i) vào kết quả. Tuy nhiên, cách đặt tên biến và vòng lặp có thể gây hiểu nhầm nếu không đọc kỹ công thức Legendre. Logic tính toán là đúng (nếu độ dài số cho phép).

Cách Vòng lặp chia 5 (Phiên bản 1)
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int count = 0;
    while (n >= 5) {
        count += n / 5;
        n /= 5;
    }
    cout << count << endl;
    return 0;
}
  • Time Complexity: O(log n)
  • Space Complexity: O(1)

Đây là cách biến thể của công thức Legendre. Thay vì tính lũy thừa 5, ta chia不断 n cho 5. Vòng lặp while (n >= 5) thực hiện: count += n/5 (đếm các số chia hết cho 5), sau đó n = n/5 (để đếm các số chia hết cho 25, 125... ở các bước tiếp theo). Đây là thuật toán chuẩn để giải bài toán đếm số 0 giai thừa.

Cách Vòng lặp lũy thừa 5 (Phiên bản 2)
#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll n;
int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);

    cin >> n;
    ll ans = 0, s = 5;
    for (int i = 1; s <= n; ++i){
        ans += n/s;
        s *= 5;
    }
    cout << ans;
    return 0;
}
  • Time Complexity: O(log n)
  • Space Complexity: O(1)

Cách tiếp cận này dùng biến s để lưu lũy thừa của 5 (bắt đầu bằng 5). Vòng lặp for chạy chừng nào s còn nhỏ hơn hoặc bằng n. Trong mỗi bước, nó cộng n/s vào kết quả và nhân s lên 5 lần. Đây là cách cài đặt trực tiếp nhất của công thức Legendre: count = n/5 + n/25 + n/125 + ...

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

Cách tiếp cận Time Space Tên
1 O(log n) O(log n) Tính trực tiếp bằng đệ quy (Có thể sai/như Solution 3)
2 O(log n) O(1) Vòng lặp chia 5 (Phiên bản 1)
3 O(log n) O(1) Vòng lặp lũy thừa 5 (Phiên bản 2)

Bài học kinh nghiệm

  • Số lượng số 0 ở cuối n! bằng số lượng cặp thừa số (2, 5) trong phân tích thừa số nguyên tố.
  • Trong n!, số lượng thừa số 2 luôn nhiều hơn số lượng thừa số 5, nên số lượng số 0 bằng số lượng thừa số 5.
  • Công thức tính số lượng thừa số 5 là tổng: floor(n/5) + floor(n/25) + floor(n/125) + ...

Lỗi thường gặp

  • Không nên tính trực tiếp giá trị n! vì kích thước số quá lớn (n <= 1000) gây tràn số (overflow).
  • Sai lầm khi đếm số lượng số chia hết cho 10 thay vì đếm thừa số 5 (ví dụ: 25 có 2 thừa số 5 nhưng chỉ chia hết cho 10 một lần).

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.