用素数计数
Count numbers with prime digit
给定一个数字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
。若N
为100
,则答案为100-36=64
。
在一个简单的情况下,如果N
是10
的幂,那么答案是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
并尝试将每个有效数字相加。
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)
.
- 没有找到相关文章