用素数计数

Count numbers with prime digit

本文关键字:      更新时间:2023-10-16

给定一个数字N,我需要找到从1到N中至少有一个素数(2,3,5或7)的数

现在N可以达到10^18。解决这个问题的最好方法是什么?

示例:设N = 100,答案为64。

请帮忙解决这个问题。

Code: main函数。但显然不是好方法

    long long int n;
    cin>>n;
    long long int count=0;
    for(int i=1;i<=n;i++){
        long long temp=i;
        while(temp>0){
            int rem=temp%10;
            if(rem==2 || rem==3 ||rem==5 || rem==7){
                count++;
                break;
            }
            temp=temp/10;
        }
    }

你认为这个问题需要编程!!

用数学来回答。

考虑互补问题,即数字中没有素数。所以只能用数字{0, 1, 4, 6, 8, 9}

举例:你用这个集合可以得到多少个2位数?答案是6*6=6^2=36。若N100,则答案为100-36=64

在一个简单的情况下,如果N10的幂,那么答案是N - 6^log(N)

那么N是不是10的幂呢?考虑N=57。在这种情况下,当第一位数字小于5时。可以用{0, 1, 4}表示第一个数字,用{0, 1, 4, 6, 8, 9}表示第二个数字。因此答案是57-3*6+1=40。(主题中不包含零,因此答案是递增的)。

quote from comment

的想法是在这里,但事情变得有点复杂,当你必须记住允许的数字,我的意思是,例如N = 645第一位数字可以是{0,1,4,6}中的一位,第二位数字依赖于第一个数字的值…(只有{0,1,4}的6,也{6,{0,1,4}中的第一个数字为8,9})

我想你可以把675这样的数字想象成600 + 70 + 5。然后,您可以单独计算不包含任何素数的每个组件的组合数量,就像上面的帖子所说的那样(但我们只针对0..599年,0 . .69年,0 . . 5)。然后从左边开始求和(600 -> 70 -> 5)只要数字的第一位不是素数。所以你把600和70的组合相加因为6不是质数,但是你不把5的组合相加因为7是质数(这意味着所有大于等于670的数都是质数(在这个例子中是7))这是不符合条件的数。你要用原来的数减去它。你还得加一个,因为你不想把0算进去。

编辑我写了这个代码

#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
    unsigned long long int number;
    unsigned long long int answer;
    std::cin >> number;
    number += 1; //because we search in range 1..n-1
    std::vector<int> digits;
    unsigned long long int numberCopy = number;
    while(numberCopy)
    {
        int digit = numberCopy % 10;
        digits.push_back(digit);
        numberCopy /= 10;
    }
    std::reverse(digits.begin(), digits.end()); //so digits are in proper order
    int numberOfDigits = digits.size();
    std::vector<unsigned long long int> partialCombinations(numberOfDigits);
    //0, 1,       4,    6,    8, 9
    //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    constexpr unsigned long long int nonprimesLessThan[11] = {0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6};
    constexpr bool prime[10] = {false, false, true, true, false, true, false, true, false, false};
    //we consider each digit as highest digit in number such that all elements in digits array after this digit are equal 0 (so with 3 right most digit = 6 we check for combinations lower than 600)
    unsigned long long int multiplayer = 1; //with numbers like 5999999 on every higher decimal place we have nonprimesLessThan[10] times more combinations 5999999 because we consider numbers that are lower (6000000-1 = 5999999)
    for(int i = numberOfDigits - 1; i >= 0; --i)
    {
        int combinations = nonprimesLessThan[digits[i]] * multiplayer;
        partialCombinations[i] = combinations;
        multiplayer *= nonprimesLessThan[10]; //with numbers like 5999999 on every higher decimal place we have nonprimesLessThan[10] times more combinations
    }
    unsigned long long int sumOfCombinations = partialCombinations[0];
    for(int i = 1; i < numberOfDigits; ++i)
    {
        if(prime[digits[i - 1]]) break;
        sumOfCombinations += partialCombinations[i];
    }
    answer = number - sumOfCombinations;
    std::cout << answer;
    return 0;
}

我不能比在评论里解释得更多了。我不知道它是否完全正确,但我无法检查它。至少看起来效果不错。

这类问题可以用非常标准的动态规划来解决:

1)让我们构造从最高有效位数到最低有效位数的数。状态是:(len, was, greater),其中len是已经处理的数字数,was是一个布尔值,表示是否已经添加了2、3、5或7,greater是一个布尔值,如果N大于当前前缀为true,如果等于它为false(注意,如果当前前缀大于N则无效)。每个状态的值是构造这种前缀的方法的个数。

2)基本情况:f(0, 0, 0) = 1。只有一种方法可以构造空前缀。要计算其他值,可以遍历len并尝试将每个有效数字相加。

下面是我的c++代码:
long long compute(string N) {
    static set<int> good({2, 3, 5, 7});
    int len = N.length();
    vector<vector<vector<long long>>> f(len + 1, 
        vector<vector<long long>>(2, vector<long long>(2)));
    f[0][0][0] = 1; //base case
    for (int i = 1; i <= len; i++)
        for (int was = 0; was <= 1; was++)
            for (int gt = 0; gt <= 1; gt++) 
                for (int d = 0; d <= 9; d++) {
                    bool n_was = was || good.count(d);
                    bool n_gt = false;
                    if (gt) {
                        n_gt = true;
                    } 
                    else {
                        if (N[i - 1] - '0' < d) //Invalid prefix
                            continue;
                        if (N[i - 1] - '0' > d)
                            n_gt = true;
                    }
                    f[i][n_was][n_gt] += f[i - 1][was][gt];
                }
    return f[len][1][0] + f[len][1][1];
}

3)时间复杂度为O(length(N)),即O(log N) .

相关文章:
  • 没有找到相关文章