200 万以下的素数之和。埃拉托斯特尼筛法

发布于 2024-11-10 14:45:09 字数 1150 浏览 0 评论 0原文

解决问题时遇到一点困难:“计算 200 万以下的素数之和”。我正在使用“埃拉托斯特尼筛法”。我的方法可以很好地查找 100 以内的素数,但是当我尝试查找 2,000,000 以内的素数之和时,我得到了错误的答案。

#include <iostream>

using namespace std;
long long unsigned int number[2000008];
int x=2000000LLU;
int sum()
{
    int s=0LLU; //stores sum
    for(int y=2; y<=x; y++) //add all the numers in the array from 2 to 2 million
    {
        s+=number[y];
    }
    return s;
}

int main()
{
    int k=2;
    for(int i=2; i<=x; i++) //fills in numbers from 2 to 2 million in the array
    {
        number[i]=i;
    }
    for(int j=2; j<=x; j+=1) //starts eliminating multiples of prime numbers from the grid
    {
        if(number[j]!=0) //moves through the grid till it finds a number that hasnt been crossed out. ie. isnt zero                            
        {
            for(int y=j+j; y<=x; y+=j) //when it finds a number, it removes all subsequent multiples of it
            {
                number[y]=0;
            }
        }

    }  
    cout<<endl<<"done"; //shows that the loop has been completed
    cout<<sum(); //outputs the sum of the grid
    return 0;
}

Having a little trouble solving Problem: 'Calculate the sum of primes below two million'. I'm using the 'Sieve of Eratosthenes' method. My method works fine for finding primes till hundred but when I try to find the sum of primes till 2,000,000 I get an incorrect answer.

#include <iostream>

using namespace std;
long long unsigned int number[2000008];
int x=2000000LLU;
int sum()
{
    int s=0LLU; //stores sum
    for(int y=2; y<=x; y++) //add all the numers in the array from 2 to 2 million
    {
        s+=number[y];
    }
    return s;
}

int main()
{
    int k=2;
    for(int i=2; i<=x; i++) //fills in numbers from 2 to 2 million in the array
    {
        number[i]=i;
    }
    for(int j=2; j<=x; j+=1) //starts eliminating multiples of prime numbers from the grid
    {
        if(number[j]!=0) //moves through the grid till it finds a number that hasnt been crossed out. ie. isnt zero                            
        {
            for(int y=j+j; y<=x; y+=j) //when it finds a number, it removes all subsequent multiples of it
            {
                number[y]=0;
            }
        }

    }  
    cout<<endl<<"done"; //shows that the loop has been completed
    cout<<sum(); //outputs the sum of the grid
    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

も让我眼熟你 2024-11-17 14:45:09

我不确定 int 是否足以保存答案...它可能大于 32 位值。
尝试在整个过程中使用long long

I'm not sure an int is enough to hold the answer... It could be larger than a 32-bit value.
Try using long long throughout.

牵强ㄟ 2024-11-17 14:45:09

通过有效地使用埃拉托斯特尼筛,我解决了这个问题,这是我的代码,它是高度优化的

public class SumOfPrime {

    static void findSum()
    {
        long i=3;
        long sum=0;
        int count=0;
        boolean[] array = new boolean[2000000];
        for(long j=0;j<array.length;j++)
        {
         if((j&1)==0)
          array[(int)j]=false;   
         else    
         array[(int)j]=true;
        }
        array[1]=false;
        array[2]=true;
        for(;i<2000000;i+=2)
        { 
            if(array[(int)i] & isPrime(i))
            {   
                array[(int)i]=true;
                //Sieve of Eratosthenes
                for(long j=i+i;j<array.length;j+=i)
                    array[(int)j]=false;
            }
        }
        for(int j=0;j<array.length;j++)
        {
            if(array[j])
            {   
             //System.out.println(j);
             count++;   
             sum+=j;
            }
        }   
        System.out.println("Sum="+sum +" Count="+count);
    }
    public static boolean isPrime(long num)
    {
        boolean flag=false;
        long i=3;
        long limit=(long)Math.sqrt(num);
        for(;i<limit && !(flag);i+=2)
        {
            if(num%i==0)
            {
                flag=false;
                break;
            }   
        }
        if(i>=limit)
         flag=true;
        return flag;
    }

    public static void main(String args[])
    {
        long start=System.currentTimeMillis();
        findSum();
        long end=System.currentTimeMillis();
        System.out.println("Time for execution="+(end-start)+"ms");
    }

}

输出是

Sum=142913828922 Count=148933
Time for execution=2360ms

如果您有疑问,请告诉

by using Sieve of Eratosthenes effectively, i solved the problem, here is my code , it is highly optimized

public class SumOfPrime {

    static void findSum()
    {
        long i=3;
        long sum=0;
        int count=0;
        boolean[] array = new boolean[2000000];
        for(long j=0;j<array.length;j++)
        {
         if((j&1)==0)
          array[(int)j]=false;   
         else    
         array[(int)j]=true;
        }
        array[1]=false;
        array[2]=true;
        for(;i<2000000;i+=2)
        { 
            if(array[(int)i] & isPrime(i))
            {   
                array[(int)i]=true;
                //Sieve of Eratosthenes
                for(long j=i+i;j<array.length;j+=i)
                    array[(int)j]=false;
            }
        }
        for(int j=0;j<array.length;j++)
        {
            if(array[j])
            {   
             //System.out.println(j);
             count++;   
             sum+=j;
            }
        }   
        System.out.println("Sum="+sum +" Count="+count);
    }
    public static boolean isPrime(long num)
    {
        boolean flag=false;
        long i=3;
        long limit=(long)Math.sqrt(num);
        for(;i<limit && !(flag);i+=2)
        {
            if(num%i==0)
            {
                flag=false;
                break;
            }   
        }
        if(i>=limit)
         flag=true;
        return flag;
    }

    public static void main(String args[])
    {
        long start=System.currentTimeMillis();
        findSum();
        long end=System.currentTimeMillis();
        System.out.println("Time for execution="+(end-start)+"ms");
    }

}

and the output is

Sum=142913828922 Count=148933
Time for execution=2360ms

if you have doubt, please do tell

凉风有信 2024-11-17 14:45:09

使用无符号 64 位整数计算 sum

@Omri Barel 的回答确定了OP代码中需要的最小修改:

尝试在全文中使用 long long。

这可行,但太过分了。仅必须使用 64 位变量计算总和。数组中的数字最好存储为 32 位数量。对于高达 200 万的值,不需要 64 位。使用 std::uint32_t 可以节省大量存储空间。

我的偏好是使用标头 中的类型别名。它们允许您说出您想要多少位。此外,我对所有下标都谨慎使用 std::size_t 类型。

如果您继续使用 OP 使用的“全局”变量,则会给出以下声明。 enum 用于创建编译时常量:

enum : std::uint32_t { x = 2'000'000u };
enum : std::size_t { array_size = static_cast<std::size_t>(x) + 1u };
std::uint32_t number[array_size];  // `x` is a valid subscript.

请参阅下面的消除全局变量的方法。

使用 std::accumulate 添加数字

如果您小心地将数组 number 的前两个元素清零,则可以使用 std::accumulate< /code> 计算总和。

  • std::cbeginstd::cend 具有接受内置数组作为参数的重载。
  • 第三个参数 std::uint64_t{} 强制 std::accumulate 使用无符号 64 位算术进行计算。
std::uint64_t sum()
{
    return std::accumulate(std::cbegin(number), std::cend(number), std::uint64_t{});
}
类型之间的转换

由于数组 number 的元素具有 std::uint32_t 类型,而其下标具有 std::size_t 类型,因此必须使用将下标分配给数组时进行强制转换。否则,您的编译器可能会发出警告,指出赋值涉及“缩小”值。

// Variable `i` is a subscript, of type `std::size_t`.
number[i] = static_cast<std::uint32_t>(i);
重新访问 OP 中的程序

这是 OP 中的完整程序,并进行了上述修改。您应该能够复制、编译和执行,而无需摆弄。

// main.cpp
// 
// A few limited fixes were made to the code below.
//   1. Change `number` type to array of `std::uint32_t`. 
//      Using `long long unsigned` for this is overkill, 
//      and wastes a lot of storage.
//   2. Change `sum` type to `std::uint64_t`, and 
//      use `std::accumulate` to add the numbers.
//   3. Use `std::size_t` for subscripts.
//   4. Delete `using namespace std;`
//   5. Delete variable `k`, which was unused.

#include <cstddef>   // size_t
#include <cstdint>   // uint32_t, uint64_t
#include <iostream>  // cout
#include <iterator>  // cbegin, cend
#include <numeric>   // accumulate

enum : std::uint32_t { x = 2000000u };
enum : std::size_t { array_size = static_cast<std::size_t>(x) + 1u };
std::uint32_t number[array_size];  // `x` is a valid subscript.

std::uint64_t sum()
{
    return std::accumulate(std::cbegin(number), std::cend(number), std::uint64_t{});
}

int main()
{
    number[0u] = 0u;  // Zero-out the first two array elements.
    number[1u] = 0u;
    for (std::size_t i = 2u; i < array_size; i++) //fills in numbers from 2 to 2 million in the array
    {
        number[i] = static_cast<std::uint32_t>(i);
    }
    for (std::size_t j = 2u; j < array_size; j++) //starts eliminating multiples of prime numbers from the grid
    {
        if (number[j] != 0u) //moves through the grid till it finds a number that hasn't been crossed out. ie. isnt zero                            
        {
            for (std::size_t y = j + j; y < array_size; y += j) //when it finds a number, it removes all subsequent multiples of it
            {
                number[y] = 0u;
            }
        }
    }
    std::cout << '\n' << "done. "; //shows that the loop has been completed
    std::cout << "sum = " << sum(); //outputs the sum of the grid
    return 0;
}
// end file: main.cpp

以下部分展示了如何在不使用全局变量的情况下解决此问题。

交错筛法和求和

我最初的方法是直接方法:创建素数向量,然后对其元素求和。函数make_vector_of_primes(未显示)实现埃拉托斯特尼筛算法,来自维基百科。

std::uint64_t calculate_sum_of_primes(std::uint32_t const n)
{
    std::vector<std::uint32_t> const v{ make_vector_of_primes(n) };
    return std::accumulate(v.cbegin(), v.cend(), std::uint64_t{});
}

经过尝试后,我发现您可以交错筛和总和的计算。该代码并不像原始代码那么干净,但程序运行速度快了大约 15% 到 20%。它还使用更少的存储空间,因为素数向量永远不会被计算。

下面的程序包含两个OP中未使用的额外优化。

  • 当索引i超过n的平方根时,筛子的外循环停止。
  • 筛子的内部循环从索引j开始,设置为i * i,而不是i + i
main.cpp
// main.cpp
// Requires C++14 or later.
#include <chrono>    // chrono, steady_clock, duration_cast
#include <cmath>     // sqrt
#include <cstddef>   // size_t
#include <cstdint>   // uint32_t, uint64_t
#include <iostream>  // cout
#include <limits>    // numeric_limits
#include <vector>    // vector

namespace
{
    //==================================================================
    // calculate_sum_of_primes
    // Calculate the sum of all prime numbers <= `n`.
    // Preconditions: 
    //   - `n` is at least 2.
    //   - `std::size_t` can hold `n + 1`, without overflowing
    //==================================================================
    std::uint64_t calculate_sum_of_primes(std::uint32_t const n)
    {
        auto const nn{ static_cast<std::size_t>(n) };
        std::vector<bool> is_prime(nn + 1u, true);  // `nn` is a valid subscript
        std::uint64_t sum{};
        is_prime[0u] = false;
        is_prime[1u] = false;
        auto const sqrt_n{ static_cast<std::size_t>(std::sqrt(n)) };
        for (std::size_t i{ 2u }; i <= sqrt_n; ++i) {
            if (is_prime[i]) {
                sum += static_cast<std::uint64_t>(i);
                for (std::size_t j{ i * i }; j <= nn; j += i) {
                    is_prime[j] = false;
                }
            }
        }
        for (std::size_t i{ sqrt_n + 1u }; i <= nn; ++i) {
            if (is_prime[i]) {
                sum += static_cast<std::uint64_t>(i);
            }
        }
        return sum;
    }
    //------------------------------------------------------------------
    void test(std::uint32_t const n)
    {
        using std::chrono::duration_cast;
        using std::chrono::milliseconds;
        auto const start{ std::chrono::steady_clock::now() };
        auto const sum{ calculate_sum_of_primes(n) };
        auto const stop{ std::chrono::steady_clock::now() };
        auto const elapsed_time{ duration_cast<milliseconds>(stop - start) };
        std::cout << 
            "The sum of prime numbers on the interval "
            "[2, " << n << "] is " << sum << ".\n"
            "Elapsed time: " << elapsed_time.count() << "ms\n\n";
    }
    //------------------------------------------------------------------
}
int main()
{
    test(2'000'000u);
    test(std::numeric_limits<std::uint32_t>::max());
    return 0;
}
// end file: main.cpp
输出

第二个示例对可以由 std::uint32_t 类型表示的所有素数求和。其中有超过 2.03 亿个,分散在 32 位可以表示的 40 亿多个数字中。在发布模式下,启用编译器优化后,只需 40 多秒即可找到所有素数并计算它们的总和。

如果搜索空间减少到区区 200 万(如 OP 所示),那么即使是这台笨重的计算机也只需几毫秒即可完成这项工作。

The sum of prime numbers on the interval [2, 2000000] is 142913828922.
Elapsed time: 9ms

The sum of prime numbers on the interval [2, 4294967295] is 425649736193687430.
Elapsed time: 41880ms

Compute sum using unsigned, 64-bit integers

The answer by @Omri Barel identified the minimal modification needed in the OP's code:

Try using long long throughout.

This works, but it goes too far. Only the sum must be computed using 64-bit variables. The numbers in the array are better stored as 32-bit quantities. For values up to 2 million, you don't need 64-bits. Using std::uint32_t saves a ton of storage.

My preference is to use the type aliases from header <cstdint>. They allow you to say how many bits you want. In addition, I am scrupulous in using type std::size_t for all subscripts.

If you stay with the "global" variables used by the OP, that gives you the following declarations. enum is used to create compile-time constants:

enum : std::uint32_t { x = 2'000'000u };
enum : std::size_t { array_size = static_cast<std::size_t>(x) + 1u };
std::uint32_t number[array_size];  // `x` is a valid subscript.

See below for a way to eliminate the globals.

Use std::accumulate to add the numbers

If you are careful to zero-out the first two elements of array number, you can use std::accumulate to calculate the sum.

  • std::cbegin and std::cend have overloads that accept built-in arrays as arguments.
  • The third argument, std::uint64_t{}, forces std::accumulate to do the computation using unsigned, 64-bit arithmetic.
std::uint64_t sum()
{
    return std::accumulate(std::cbegin(number), std::cend(number), std::uint64_t{});
}
Casting between types

Because the elements of array number have type std::uint32_t, while its subscripts have type std::size_t, you must use a cast when a subscript is assigned into the array. Otherwise, your compiler may emit a warning that the assignment involves "narrowing" the value.

// Variable `i` is a subscript, of type `std::size_t`.
number[i] = static_cast<std::uint32_t>(i);
Revisiting the program from the OP

Here is the complete program from the OP, with the modifications discussed above. You should be able to copy, compile, and execute, without fiddling.

// main.cpp
// 
// A few limited fixes were made to the code below.
//   1. Change `number` type to array of `std::uint32_t`. 
//      Using `long long unsigned` for this is overkill, 
//      and wastes a lot of storage.
//   2. Change `sum` type to `std::uint64_t`, and 
//      use `std::accumulate` to add the numbers.
//   3. Use `std::size_t` for subscripts.
//   4. Delete `using namespace std;`
//   5. Delete variable `k`, which was unused.

#include <cstddef>   // size_t
#include <cstdint>   // uint32_t, uint64_t
#include <iostream>  // cout
#include <iterator>  // cbegin, cend
#include <numeric>   // accumulate

enum : std::uint32_t { x = 2000000u };
enum : std::size_t { array_size = static_cast<std::size_t>(x) + 1u };
std::uint32_t number[array_size];  // `x` is a valid subscript.

std::uint64_t sum()
{
    return std::accumulate(std::cbegin(number), std::cend(number), std::uint64_t{});
}

int main()
{
    number[0u] = 0u;  // Zero-out the first two array elements.
    number[1u] = 0u;
    for (std::size_t i = 2u; i < array_size; i++) //fills in numbers from 2 to 2 million in the array
    {
        number[i] = static_cast<std::uint32_t>(i);
    }
    for (std::size_t j = 2u; j < array_size; j++) //starts eliminating multiples of prime numbers from the grid
    {
        if (number[j] != 0u) //moves through the grid till it finds a number that hasn't been crossed out. ie. isnt zero                            
        {
            for (std::size_t y = j + j; y < array_size; y += j) //when it finds a number, it removes all subsequent multiples of it
            {
                number[y] = 0u;
            }
        }
    }
    std::cout << '\n' << "done. "; //shows that the loop has been completed
    std::cout << "sum = " << sum(); //outputs the sum of the grid
    return 0;
}
// end file: main.cpp

The following section shows how to solve this without using global variables.

Interleaving sieve and sum

My original approach was the direct one: create a vector of primes, and then sum its elements. Function make_vector_of_primes (not shown) implements the Sieve of Eratosthenes algorithm, from Wikipedia.

std::uint64_t calculate_sum_of_primes(std::uint32_t const n)
{
    std::vector<std::uint32_t> const v{ make_vector_of_primes(n) };
    return std::accumulate(v.cbegin(), v.cend(), std::uint64_t{});
}

After playing around with this, I discovered that you can interleave the computation of sieve and sum. The code is not nearly as clean as the original, but the program runs about 15% to 20% faster. It also uses way less storage, because the vector of primes is never computed.

The program below contains two additional optimizations that were not used in the OP.

  • The outer loop of the sieve stops when index i exceeds the square root of n.
  • The inner loop of the sieve starts with index j set to i * i, rather than i + i.
main.cpp
// main.cpp
// Requires C++14 or later.
#include <chrono>    // chrono, steady_clock, duration_cast
#include <cmath>     // sqrt
#include <cstddef>   // size_t
#include <cstdint>   // uint32_t, uint64_t
#include <iostream>  // cout
#include <limits>    // numeric_limits
#include <vector>    // vector

namespace
{
    //==================================================================
    // calculate_sum_of_primes
    // Calculate the sum of all prime numbers <= `n`.
    // Preconditions: 
    //   - `n` is at least 2.
    //   - `std::size_t` can hold `n + 1`, without overflowing
    //==================================================================
    std::uint64_t calculate_sum_of_primes(std::uint32_t const n)
    {
        auto const nn{ static_cast<std::size_t>(n) };
        std::vector<bool> is_prime(nn + 1u, true);  // `nn` is a valid subscript
        std::uint64_t sum{};
        is_prime[0u] = false;
        is_prime[1u] = false;
        auto const sqrt_n{ static_cast<std::size_t>(std::sqrt(n)) };
        for (std::size_t i{ 2u }; i <= sqrt_n; ++i) {
            if (is_prime[i]) {
                sum += static_cast<std::uint64_t>(i);
                for (std::size_t j{ i * i }; j <= nn; j += i) {
                    is_prime[j] = false;
                }
            }
        }
        for (std::size_t i{ sqrt_n + 1u }; i <= nn; ++i) {
            if (is_prime[i]) {
                sum += static_cast<std::uint64_t>(i);
            }
        }
        return sum;
    }
    //------------------------------------------------------------------
    void test(std::uint32_t const n)
    {
        using std::chrono::duration_cast;
        using std::chrono::milliseconds;
        auto const start{ std::chrono::steady_clock::now() };
        auto const sum{ calculate_sum_of_primes(n) };
        auto const stop{ std::chrono::steady_clock::now() };
        auto const elapsed_time{ duration_cast<milliseconds>(stop - start) };
        std::cout << 
            "The sum of prime numbers on the interval "
            "[2, " << n << "] is " << sum << ".\n"
            "Elapsed time: " << elapsed_time.count() << "ms\n\n";
    }
    //------------------------------------------------------------------
}
int main()
{
    test(2'000'000u);
    test(std::numeric_limits<std::uint32_t>::max());
    return 0;
}
// end file: main.cpp
Output

The second example sums all primes that can be represented by type std::uint32_t. There are more than 203 million of them, scattered among the 4 billion-plus numbers that can be represented using 32-bits. In release mode, with compiler optimizations enabled, it took just over 40 seconds to find all the primes, and calculate their sum.

If the search space is reduced to a paltry 2 million, as in the OP, then even this clunky computer requires mere milliseconds to do the job.

The sum of prime numbers on the interval [2, 2000000] is 142913828922.
Elapsed time: 9ms

The sum of prime numbers on the interval [2, 4294967295] is 425649736193687430.
Elapsed time: 41880ms
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文