Dem từ 1 đến n có bao nhiêu số dương năm 2024

Ada, Assembly, Awk, C, C++, C11, CLANG, CLANGX, Classical, COBOL, Coffee, CSC, D lang, DART, F95, FORTH, Fortrn,

GAS32

, GO, Haskell, Itercal, Java, kotlin,

LEAN

, LISP, LUA, MONOVB,

Nasm

,

OCAML

, Pascal, Perl, php, PIKE,

prolog

, Pypy, Python,

Ruby 2

, RUST, Scala, SCM, SED,

SWIFT

, TCL,

TUR

,

V8JS

,

VB

, ZIG

Toto học về hệ đếm cơ số 2 và rất thích thú với các phép toán trên nó. Tichpx thấy vậy đặt ra bài toán, cho số nguyên dương n nếu viết tất cả các số từ 1 đến n sang hệ nhị phận thì cần bao nhiêu bit số 1. Toto rất hào hứng với bài toán nhưng vẫn chưa có lời giải. Bạn hãy lập trình giải bài toán giúp Toto nhé

I. Đặt vấn đề

Chắc hẳn, ai trong chúng ta cũng đã quá quen thuộc với bài toán đếm số ước nguyên dương của nn. Giải thuật thông thường nhất mà mọi người thường sử dụng là giải thuật O[n],O[\sqrt{n}], dựa trên một nhận định rằng nếu như số nn có một ước là i [1≤i≤n]i \ [1 \le i \le \sqrt{n}] thì nó cũng sẽ có một ước nữa là ni [n≤ni≤n]\frac{n}{i} \ \left[\sqrt{n} \le \frac{n}{i} \le n \right]. Bằng phương pháp này, chúng ta có thể giải quyết mọi bài toán với giới hạn nn khoảng 101510^{15} trở xuống [Các bạn xem lại trong chuyên đề Tìm các ước của một số nguyên].

Một phương pháp hay khác mà chúng ta cũng sử dụng là phân tích thừa số nguyên tố và đếm ước của nn dựa trên phân tích nguyên tố của nó. Cách làm này có thể khiến thao tác đếm ước của số nn giảm xuống độ phức tạp O[log⁡[n]]O[\log[n]] khi kết hợp với sàng lọc số nguyên tố, và thường được áp dụng trong các bài toán multi-testcase [các bạn xem lại trong chuyên đề Số nguyên tố]. Tuy nhiên, nhược điểm của phương pháp này là bạn buộc phải tạo ra được một mảng có độ dài nn để đánh dấu các số nguyên tố, đồng nghĩa với việc nếu n≤109,n \le 10^9, các bạn không thể sử dụng được nó.

Điều gì sẽ xảy ra khi chúng ta cần đếm ước của một số nguyên dương n≤1018?n \le 10^{18}? Phân tích thừa số nguyên tố? Không thể, vì ta không thể sàng lọc được số nguyên tố ở giới hạn này. Vậy phân tích theo phương pháp O[n]O[\sqrt{n}] truyền thống thì sao? Cũng không thể, vì O[n]≈O[109],O[\sqrt{n}] \approx O[10^9], độ phức tạp này không đủ tốt. Khi đó, người ta sử dụng phương pháp đếm ước trong O[n13],O[n^{\frac{1}{3}}], một phương pháp rất hiệu quả nhưng lại được ít người biết đến, có lẽ vì chúng ta không thường xuyên gặp phải những bài toán như vậy. Trong chuyên đề này, chúng ta sẽ cùng nghiên cứu ý tưởng và cách cài đặt của phương pháp này bằng C++. Trước khi đọc bài viết, các bạn cần có kiến thức đầy đủ về sàng lọc số nguyên tố, đếm ước theo phương pháp thông thường cũng như kĩ năng code ở mức khá. Nếu chưa nắm được những kiến thức này, các bạn hãy quay lại nghiên cứu những chuyên đề cũ mà mình đã để link ở trên nhé!

II. Phương pháp đếm số ước của một số trong O[n13]O[n^{\frac{1}{3}}]

1. Phương pháp kiểm tra nguyên tố Fermat

Để sử dụng được giải thuật đếm ước trong O[n13],O[n^{\frac{1}{3}}], trước tiên ta cần tìm hiểu về phương pháp của Fermat dùng để kiểm tra tính nguyên tố của một số. Đây là một phương pháp kiểm tra nguyên tố có tính xác suất, nghĩa là nó có thể xảy ra trường hợp sai, tuy nhiên, trong giải thuật này sự sai khác đó có thể chấp nhận được.

Ý tưởng: Phương pháp kiểm tra tính nguyên tố của Fermat được xây dựng dựa trên định lý Fermat nhỏ: Nếu nn là một số nguyên tố, thì với mọi giá trị aa sao cho 1≤a 0 \ [1].

  • Theo định lý nhỏ Fermat, nếu nn là một số nguyên tố, thì với mọi giá trị aa sao cho 1≤a 0.
  • Bước 22: Chọn ngẫu nhiên aa thuộc [1,n−1][1, n - 1] rồi đặt \text{mod_val} = a^d \text{ mod } n.
  • Bước 33: Liên tục bình phương \text{mod_val} và lấy số dư khi chia cho n,n, nếu như gặp một số dư khác 11 hoặc n−1 [n - 1 \ [ tương tự với −1]-1] thì trả về false\text{false}.
  • Bước 44: Nếu như sau khi kiểm tra mọi xk mod nx_k \text{ mod } n mà không tồn tại giá trị nào khác 11 hoặc n−1n - 1 thì kết luận true\text{true}.
  • Thực hiện giải thuật trên kk lần, với kk càng lớn ta sẽ có độ chính xác càng cao. Đối với giải thuật Miller - Rabin thì chỉ cần sử dụng k=10k = 10 là đã đủ an toàn.

    Cài đặt:

    // Phép nhân Ấn Độ [a * b] % mod. Sử dụng để tránh tràn số khi thực hiện phép nhân.
    int indian_multiplication[int a, int b, int mod]
    {
        if [b == 0]
            return 0;
        int half = indian_multiplication[a, b / 2LL, mod] % mod;
        if [b & 1]
            return [half + half + a] % mod;
        else
            return [half + half] % mod;
    }
    // Tính [a^b] % mod. Sử dụng kết hợp với phép nhân Ấn Độ để tránh tràn số khi thực hiện phép nhân.
    int modular_exponentiation[int a, int b, int mod]
    {
        if [b == 0]
            return 1LL;
        int half = modular_exponentiation[a, b / 2LL, mod] % mod;
        int product = indian_multiplication[half, half, mod];
        if [b & 1]
            return indian_multiplication[product, a, mod];
        else
            return product;
    }
    vector < int > eratosthenes_sieve[int max_value]
    {
        vector < bool > is_prime[max_value + 1, true];
        is_prime[0] = is_prime[1] = false;
        for [int i = 2; i * i 

    Chủ Đề