寻找幸运数字的算法
Posted
技术标签:
【中文标题】寻找幸运数字的算法【英文标题】:Algorithm to find Lucky Numbers 【发布时间】:2012-02-19 13:48:59 【问题描述】:我遇到了这个问题。如果一个数字的数字之和以及数字的平方和是质数,则该数字称为幸运数字。 A和B之间有多少个数字是幸运的? 1 18。我试过了。
首先,我生成了介于 1 和平方和 (81 *18 = 1458) 之间的所有可能素数。 我在 A 和 B 中读取了如果 B 是 2 位数字(最大数字是 18 由 99 生成),则可以通过对数字求和生成的最大数字。 对于介于 1 之间的每个质数是最大数。我应用了整数分区算法。 对于每个可能的分区,我检查了它们的数字平方和是否形成素数。如果是这样,则生成该分区的可能排列,并且如果它们位于范围内,则它们是幸运数字。这是实现:
#include<stdio.h>
#include<malloc.h>
#include<math.h>
#include <stdlib.h>
#include<string.h>
long long luckynumbers;
int primelist[1500];
int checklucky(long long possible,long long a,long long b)
int prime =0;
while(possible>0)
prime+=pow((possible%10),(float)2);
possible/=10;
if(primelist[prime]) return 1;
else return 0;
long long getmax(int numdigits)
if(numdigits == 0) return 1;
long long maxnum =10;
while(numdigits>1)
maxnum = maxnum *10;
numdigits-=1;
return maxnum;
void permuteandcheck(char *topermute,int d,long long a,long long b,int digits)
if(d == strlen(topermute))
long long possible=atoll(topermute);
if(possible >= getmax(strlen(topermute)-1)) // to skip the case of getting already read numbers like 21 and 021(permuted-210
if(possible >= a && possible <= b)
luckynumbers++;
else
char lastswap ='\0';
int i;
char temp;
for(i=d;i<strlen(topermute);i++)
if(lastswap == topermute[i])
continue;
else
lastswap = topermute[i];
temp = topermute[d];
topermute[d] = topermute[i];
topermute[i] = temp;
permuteandcheck(topermute,d+1,a,b,digits);
temp = topermute[d];
topermute[d] = topermute[i];
topermute[i] = temp;
void findlucky(long long possible,long long a,long long b,int digits)
int i =0;
if(checklucky(possible,a,b))
char topermute[18];
sprintf(topermute,"%lld",possible);
permuteandcheck(topermute,0,a,b,digits);
void partitiongenerator(int k,int n,int numdigits,long long possible,long long a,long long b,int digits)
if(k > n || numdigits > digits-1 || k > 9) return;
if(k == n)
possible+=(k*getmax(numdigits));
findlucky(possible,a,b,digits);
return;
partitiongenerator(k,n-k,numdigits+1,(possible + k*getmax(numdigits)),a,b,digits);
partitiongenerator(k+1,n,numdigits,possible,a,b,digits);
void calcluckynumbers(long long a,long long b)
int i;
int numdigits = 0;
long long temp = b;
while(temp > 0)
numdigits++;
temp/=10;
long long maxnum =getmax(numdigits)-1;
int maxprime=0,minprime =0;
temp = maxnum;
while(temp>0)
maxprime+=(temp%10);
temp/=10;
int start = 2;
for(;start <= maxprime ;start++)
if(primelist[start])
partitiongenerator(0,start,0,0,a,b,numdigits);
void generateprime()
int i = 0;
for(i=0;i<1500;i++)
primelist[i] = 1;
primelist[0] = 0;
primelist[1] = 0;
int candidate = 2;
int topCandidate = 1499;
int thisFactor = 2;
while(thisFactor * thisFactor <= topCandidate)
int mark = thisFactor + thisFactor;
while(mark <= topCandidate)
*(primelist + mark) = 0;
mark += thisFactor;
thisFactor++;
while(thisFactor <= topCandidate && *(primelist+thisFactor) == 0) thisFactor++;
int main()
char input[100];
int cases=0,casedone=0;
long long a,b;
generateprime();
fscanf(stdin,"%d",&cases);
while(casedone < cases)
luckynumbers = 0;
fscanf(stdin,"%lld %lld",&a,&b);
int i =0;
calcluckynumbers(a,b);
casedone++;
算法太慢了。我认为可以根据数字的属性找到答案。请分享您的想法。谢谢。
【问题讨论】:
Bug:primelist
的维度为 1400,但您将其视为维度为 1500
我觉得这个问题应该移到codereview.stackexchange.com
@Paul R,我认为这没什么大不了的
@batbaatar:你认为超出数组末尾的写入“没什么大不了的”???
@Muad'Dib:这不是家庭作业。但它来自网站 InterviewStreet.com。解决问题是一回事。在他们分配的时间内解决它完全是另一头野兽。
【参考方案1】:
优秀的解决方案 OleGG,但是你的代码没有优化。我对您的代码进行了以下更改,
count_lucky 函数中 k 不需要经过 9*9*i,因为对于 10000 例它会运行那么多次,相反我通过 start 和 end 减少了这个值。
我使用 ans 数组来存储中间结果。它可能看起来不多,但超过 10000 个案例,这是减少时间的主要因素。
我已经测试了这段代码,它通过了所有的测试用例。这是修改后的代码:
#include <stdio.h>
const int MAX_LENGTH = 18;
const int MAX_SUM = 162;
const int MAX_SQUARE_SUM = 1458;
int primes[1460];
unsigned long long dyn_table[20][164][1460];
//changed here.......1
unsigned long long ans[19][10][164][1460]; //about 45 MB
int start[19][163];
int end[19][163];
//upto here.........1
void gen_primes()
for (int i = 0; i <= MAX_SQUARE_SUM; ++i)
primes[i] = 1;
primes[0] = primes[1] = 0;
for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i)
if (!primes[i])
continue;
for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j)
primes[i*j] = 0;
void gen_table()
for (int i = 0; i <= MAX_LENGTH; ++i)
for (int j = 0; j <= MAX_SUM; ++j)
for (int k = 0; k <= MAX_SQUARE_SUM; ++k)
dyn_table[i][j][k] = 0;
dyn_table[0][0][0] = 1;
for (int i = 0; i < MAX_LENGTH; ++i)
for (int j = 0; j <= 9 * i; ++j)
for (int k = 0; k <= 9 * 9 * i; ++k)
for (int l = 0; l < 10; ++l)
dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k];
unsigned long long count_lucky (unsigned long long maxp)
unsigned long long result = 0;
int len = 0;
int split_max[MAX_LENGTH];
while (maxp)
split_max[len] = maxp % 10;
maxp /= 10;
++len;
int sum = 0;
int sq_sum = 0;
unsigned long long step_result;
unsigned long long step_;
for (int i = len-1; i >= 0; --i)
step_result = 0;
int x1 = 9*i;
for (int l = 0; l < split_max[i]; ++l)
//changed here........2
step_ = 0;
if(ans[i][l][sum][sq_sum]!=0)
step_result +=ans[i][l][sum][sq_sum];
continue;
int y = l + sum;
int x = l*l + sq_sum;
for (int j = 0; j <= x1; ++j)
if(primes[j + y])
for (int k=start[i][j]; k<=end[i][j]; ++k)
if (primes[k + x])
step_result += dyn_table[i][j][k];
step_+=dyn_table[i][j][k];
ans[i][l][sum][sq_sum] = step_;
//upto here...............2
result += step_result;
sum += split_max[i];
sq_sum += split_max[i] * split_max[i];
if (primes[sum] && primes[sq_sum])
++result;
return result;
int main(int argc, char** argv)
gen_primes();
gen_table();
//changed here..........3
for(int i=0;i<=18;i++)
for(int j=0;j<=163;j++)
for(int k=0;k<=1458;k++)
if(dyn_table[i][j][k]!=0ll)
start[i][j] = k;
break;
for(int k=1460;k>=0;k--)
if(dyn_table[i][j][k]!=0ll)
end[i][j]=k;
break;
//upto here..........3
int cases = 0;
scanf("%d",&cases);
for (int i = 0; i < cases; ++i)
unsigned long long a, b;
scanf("%lld %lld", &a, &b);
//changed here......4
if(b == 1000000000000000000ll)
b--;
//upto here.........4
printf("%lld\n", count_lucky(b) - count_lucky(a-1));
return 0;
解释:
gen_primes() 和 gen_table() 几乎是不言自明的。
count_lucky() 的工作原理如下:
在 split_max[] 中拆分数字,只存储个位、十位、百位等位置的单个数字。 思路是:假设 split_map[2] = 7,所以我们需要计算结果为
百位中的 1 和所有 00 到 99。
2 在百位,所有 00 到 99。
。 .
7 在百位和所有 00 到 99。
这实际上是根据已预先计算的数字总和和数字平方和来完成的(在 l 循环中)。 对于此示例: sum 将在 0 到 9*i 之间变化,平方和将在 0 到 9*9*i 之间变化...这是在 j 和 k 循环中完成的。 在 i 循环中重复所有长度
这就是 OleGG 的想法。
考虑以下优化:
从 0 到 9*9*i 的平方和是没用的,因为对于特定的数字和它不会达到整个范围。就像如果 i = 3 并且 sum 等于 5 那么平方和不会在 0 到 9*9*3 之间变化。这部分使用预先计算的值存储在 start[] 和 end[] 数组中。
存储特定位数和最高有效位置处的特定数字以及特定总和和特定平方和的值以供记忆。它太长了,但仍然大约 45 MB。 我相信这可以进一步优化。
【讨论】:
恭喜,你已经完成了。我知道诀窍必须是存储中间结果,而且我什至对这些结果如何代表原始数字的细分有正确的想法(请参阅上面的“00000-54321”示例的答案)。但是,我未能像您那样使用 [19][10][164][1460] 数组正确实现它。太棒了......这个答案值得一个复选标记和更多的赞成票。旁注:你用 k 做的诡计无关紧要......最大的收益来自ans[]
(20x 增益)和将if(primes[j + y])
移出循环(2x 增益)。总共获得约 40 倍的收益。
忽略我上面的“20x”和“2x”cmets。这些仅对我正在运行的某个测试用例有意义。显然,这取决于测试用例的总数。无论如何,这仍然是最大的两个改进。
InterviewStreet 应该是一项个人挑战!提供建议和帮助走上正轨是一回事,发布纯代码解决方案是另一回事......
我认为提供完整的解决方案会让您的想法更加具体。无论如何,比赛已经结束了,所以没有造成任何伤害。这可能不合适,但确实很有帮助。【参考方案2】:
您应该使用 DP 来完成此任务。这是我的解决方案:
#include <stdio.h>
const int MAX_LENGTH = 18;
const int MAX_SUM = 162;
const int MAX_SQUARE_SUM = 1458;
int primes[1459];
long long dyn_table[19][163][1459];
void gen_primes()
for (int i = 0; i <= MAX_SQUARE_SUM; ++i)
primes[i] = 1;
primes[0] = primes[1] = 0;
for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i)
if (!primes[i])
continue;
for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j)
primes[i*j] = 0;
void gen_table()
for (int i = 0; i <= MAX_LENGTH; ++i)
for (int j = 0; j <= MAX_SUM; ++j)
for (int k = 0; k <= MAX_SQUARE_SUM; ++k)
dyn_table[i][j][k] = 0;
dyn_table[0][0][0] = 1;
for (int i = 0; i < MAX_LENGTH; ++i)
for (int j = 0; j <= 9 * i; ++j)
for (int k = 0; k <= 9 * 9 * i; ++k)
for (int l = 0; l < 10; ++l)
dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k];
long long count_lucky (long long max)
long long result = 0;
int len = 0;
int split_max[MAX_LENGTH];
while (max)
split_max[len] = max % 10;
max /= 10;
++len;
int sum = 0;
int sq_sum = 0;
for (int i = len-1; i >= 0; --i)
long long step_result = 0;
for (int l = 0; l < split_max[i]; ++l)
for (int j = 0; j <= 9 * i; ++j)
for (int k = 0; k <= 9 * 9 * i; ++k)
if (primes[j + l + sum] && primes[k + l*l + sq_sum])
step_result += dyn_table[i][j][k];
result += step_result;
sum += split_max[i];
sq_sum += split_max[i] * split_max[i];
if (primes[sum] && primes[sq_sum])
++result;
return result;
int main(int argc, char** argv)
gen_primes();
gen_table();
int cases = 0;
scanf("%d", &cases);
for (int i = 0; i < cases; ++i)
long long a, b;
scanf("%lld %lld", &a, &b);
printf("%lld\n", count_lucky(b) - count_lucky(a-1));
return 0;
简要说明:
我正在使用 Eratosthenes 方法计算最大为 9 * 9 * MAX_LENGTH 的所有素数; 稍后,使用 DP,我正在构建表 dyn_table,其中 dyn_table[i][j][k] 中的值 X 意味着我们正好有 X 个长度为 i 的数字,数字之和等于 j,其平方和等于 k 然后我们可以很容易地计算从 1 到 999..999 的幸运数字数量(len 次 9)。为此,我们只需总结所有 dyn_table[len][j][k],其中 j 和 k 都是素数。 为了计算从 1 到随机 X 的幸运数字的数量,我们将区间从 1 到 X 拆分为长度等于 10^K 的区间(参见 *count_lucky* 函数)。 最后一步是从 count_lucky(b) 中减去 count_lucky(a-1)(因为我们在区间中包含 a)。就是这样。 O(log(MAX_NUMBER)^3)的预计算工作,每一步也有这个复杂度。
我已经针对线性直接解决方案测试了我的解决方案,结果是相同的
【讨论】:
这是一个非常聪明的解决方案。不幸的是,它不能 100% 工作,因为你不能将每个数字分成 10 的幂。我有一个不同的解决方案,它也需要类似的拆分,并且找不到像 1234567 这样的数字,尽管 1100111会很好地分成 10 的幂。大多数数字分成 10 的 倍数,我认为这不太适合你的桌子。 感谢您的评论,我确实在我的算法中发现了一个错误,所以我已经更新了它。现在我已经对从 1 到百万的所有数字进行了测试,结果与直接解决方案相同。循环for (int l = 0; l < split_max[i]; ++l)
不仅可以让我们处理像 1100111 这样的数字,还可以处理所有数字——我考虑到分割部分的第一个数字不仅可以是 0 或 1。
感谢您的更新。我试图自己想出一个与您的算法类似的模型,但您的更好。 :-)
是的,我已经针对单个查询的情况完成了我的解决方案,您的解决方案中的多个查询处理得更好,所以我 +1 =)
仍然不够快。绞尽脑汁好几天试图找到更好的优化。一个非常令人沮丧的令人上瘾的谜题!【参考方案3】:
不是枚举数字的空间,而是枚举幸运数字的不同“签名”。然后打印所有这些不同的组合。
这可以通过简单的回溯来完成:
#define _GNU_SOURCE
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#define bitsizeof(e) (CHAR_BIT * sizeof(e))
#define countof(e) (sizeof(e) / sizeof((e)[0]))
#define BITMASK_NTH(type_t, n) ( ((type_t)1) << ((n) & (bitsizeof(type_t) - 1)))
#define OP_BIT(bits, n, shift, op) \
((bits)[(unsigned)(n) / (shift)] op BITMASK_NTH(typeof(*(bits)), n))
#define TST_BIT(bits, n) OP_BIT(bits, n, bitsizeof(*(bits)), & )
#define SET_BIT(bits, n) (void)OP_BIT(bits, n, bitsizeof(*(bits)), |= )
/* fast is_prime */
static uint32_t primes_below_1M[(1U << 20) / bitsizeof(uint32_t)];
static void compute_primes_below_1M(void)
SET_BIT(primes_below_1M, 0);
SET_BIT(primes_below_1M, 1);
for (uint32_t i = 2; i < bitsizeof(primes_below_1M); i++)
if (TST_BIT(primes_below_1M, i))
continue;
for (uint32_t j = i * 2; j < bitsizeof(primes_below_1M); j += i)
SET_BIT(primes_below_1M, j);
static bool is_prime(uint64_t n)
assert (n < bitsizeof(primes_below_1M));
return !TST_BIT(primes_below_1M, n);
/* */
static uint32_t prime_checks, found;
static char sig[10];
static uint32_t sum, square_sum;
static void backtrack(int startdigit, int ndigits, int maxdigit)
ndigits++;
for (int i = startdigit; i <= maxdigit; i++)
sig[i]++;
sum += i;
square_sum += i * i;
prime_checks++;
if (is_prime(sum) && is_prime(square_sum))
found++;
if (ndigits < 18)
backtrack(0, ndigits, i);
sig[i]--;
sum -= i;
square_sum -= i * i;
int main(void)
compute_primes_below_1M();
backtrack(1, 0, 9);
printf("did %d signature checks, found %d lucky signatures\n",
prime_checks, found);
return 0;
当我运行它时:
$ time ./lucky
did 13123091 signature checks, found 933553 lucky signatures
./lucky 0.20s user 0.00s system 99% cpu 0.201 total
您想要生成可以使用该数字构建的所有不同的数字排列,而不是 found++。我还预先计算了前 1M 的素数。
我没有检查代码是否 100% 正确,您可能需要稍微调试一下。但粗略的想法就在这里,我能够在 0.2 秒以下生成所有幸运排列(即使没有错误,它的速度也不应该超过两倍)。
当然,您希望生成验证 A
(注意:开头的简介是因为我剪切并粘贴了我为项目 euler 编写的代码,因此非常快的 is_prime 适用于 N
【讨论】:
是的,我想这就是我要说的。【参考方案4】:对于那些还不知道的人来说,这是 InterviewStreet.com 网站上的一个问题(在我看来,这是最困难的一个)。我的方法开始类似于(并受到)OleGG 下面的启发。但是,在创建了他创建的第一个 [19][163][1459] 表(我将其称为 table1)之后,我的方向略有不同。我创建了第二个参差不齐的长度表 [19][x][3] (table2),其中 x 是对应位数的唯一和对的数量。对于表格的第三个维度,长度为 3,第一个元素是唯一“和对”的数量,其中 sum 和 squareSum 值由第二个和第三个元素保存。
例如:
//pseudocode
table2[1] = new long[10][3]
table2[1] = 1, 0, 0, 1, 1, 1, 1, 2, 4,
1, 3, 9, 1, 4, 16, 1, 5, 25,
1, 6, 36, 1, 7, 49, 1, 8, 64, 1, 9, 81
table2[2] = new long[55][3]
table2[3] = new long[204][3]
table2[4] = new long[518][3]
.
.
.
.
table2[17] = new long[15552][3]
table2[18] = new long[17547][3]
数组的第二维长度(10、55、204、518、...、15552、17547)可以通过查询 table1 来验证,并且可以以类似的方式填充 table2。现在使用 table2,我们可以比 OleGG 的发布方法更快地解决大型“幸运”查询,尽管仍然使用与他类似的“拆分”过程。例如,如果你需要找到 lucky(00000-54321)(即 0 到 54321 之间的幸运数字),它分解为以下 5 行的总和:
lucky(00000-54321) =
lucky(00000-49999) +
lucky(50000-53999) +
lucky(54000-54299) +
lucky(54300-53319) +
lucky(54320-54321)
进一步细分:
lucky(00000-49999) =
lucky(00000-09999) +
lucky(10000-19999) +
lucky(20000-29999) +
lucky(30000-39999) +
lucky(40000-49999)
.
.
lucky(54000-54299) =
lucky(54000-54099) +
lucky(54100-54199) +
lucky(54200-54299)
.
.
.
etc
这些值中的每一个都可以通过查询 table2 轻松获得。比如lucky(40000-49999)是在第三维table2的第2个和第3个元素加4和16得到的:
sum = 0
for (i = 0; i < 518; i++)
if (isPrime[table2[4][i][1] + 4] && isPrime[table2[4][i][2] + 4*4])
sum += table2[4][i][0]
return sum
或者幸运(54200-54299):
sum = 0
for (i = 0; i < 55; i++)
if (isPrime[table2[2][i][1] + (5+4+2)]
&& isPrime[table2[2][i][2] + (5*5+4*4+2*2)])
sum += table2[2][i][0]
return sum
现在,OleGG 的解决方案的运行速度明显快于我之前尝试过的任何其他解决方案,但是通过我上面描述的修改,它的性能甚至比以前更好(对于大型测试集大约提高了 100 倍)。但是,对于 InterviewStreet 上给出的盲测用例来说,它仍然不够快。通过一些巧妙的破解,我能够确定我目前的运行速度慢了大约 20 倍,无法在规定的时间内完成他们的测试集。但是,我找不到进一步的优化。这里最大的时间槽显然是遍历 table2 的第二维,避免这种情况的唯一方法是将这些总和的结果制成表格。但是,在给定的时间内(5 秒)计算它们或将它们全部存储在给定的空间(256MB)中的可能性太多。例如,上面的 lucky(54200-54299) 循环可以预先计算并存储为单个值,但如果是这样,我们还需要预先计算 lucky(123000200-123000299) 和 lucky(99999200-99999299) ) 等等。我已经完成了数学计算,但预先计算的计算量太多了。
【讨论】:
【参考方案5】:我刚刚解决了这个问题。DP[n](sum-square_sum)
为DP函数,DP[n](sum-square_sum)
为所有位数小于或等于n的数的个数,该数位数的sum和square_sum分别用sum和square_sum表示。例如:
DP[1](1-1) = 1 # only 1 satisfies the condition
DP[2](1-1) = 2 # both 1 and 10 satisfies the condition
DP[3](1-1) = 3 # 1 10 100
DP[3](2-4) = 3 # 11 110 101
既然我们可以很容易的算出第一个DP状态DP[1][..][..],那就是:
(0-0) => 1 (1-1) => 1 (2-4) => 1 (3-9) => 1 (4-16) => 1
(5-25) => 1 (6-36) => 1 (7-49) => 1 (8-64) => 1 (9-81) => 1
那么我们可以从 DP[1] 推导出 DP[1],然后 DP[3] ... DP[18] 上面的推论是因为每次 n 增加 1 时,例如从 DP[1] 到 DP[2],我们得到一个新数字 (0..9),以及 (sum, square_sum) 的集合) 对(即 DP[n])必须更新。
最后,我们可以遍历DP[18]集合并统计幸运数字。
那么,上面算法的时间和空间复杂度如何? 我们知道 sum
ruby lucky_numbers.rb 0.55s user 0.00s system 99% cpu 0.556 total
我通过使用蛮力算法编写一个测试函数来测试我的程序,它适用于小于 10^7 的数字。
【讨论】:
【参考方案6】:根据要求,您可以采用不同的方式进行操作。如果我这样做,我会在所需范围(A 到 (9*2)*B.length)内使用“Eratosthenes 筛”计算质数,缓存它们(同样,根据您的设置,您可以在-内存或磁盘缓存)并将其用于下一次运行。
我刚刚编写了一个快速解决方案(Java),如下所示(注意:未检查整数溢出。只是一个快速示例。另外,我的代码没有优化。):
import java.util.ArrayList;
import java.util.Arrays;
public class LuckyNumbers
public static void main(String[] args)
int a = 0, b = 1000;
LuckyNumbers luckyNums = new LuckyNumbers();
ArrayList<Integer> luckyList = luckyNums.findLuckyNums(a, b);
System.out.println(luckyList);
private ArrayList<Integer> findLuckyNums(int a, int b)
ArrayList<Integer> luckyList = new ArrayList<Integer>();
int size = ("" + b).length();
int maxNum = 81 * 4; //9*2*b.length() - 9 is used, coz it's the max digit
System.out.println("Size : " + size + " MaxNum : " + maxNum);
boolean[] primeArray = sieve(maxNum);
for(int i=a;i<=b;i++)
String num = "" + i;
int sumDigits = 0;
int sumSquareDigits = 0;
for(int j=0;j<num.length();j++)
int digit = Integer.valueOf("" + num.charAt(j));
sumDigits += digit;
sumSquareDigits += Math.pow(digit, 2);
if(primeArray[sumDigits] && primeArray[sumSquareDigits])
luckyList.add(i);
return luckyList;
private boolean[] sieve(int n)
boolean[] prime = new boolean[n + 1];
Arrays.fill(prime, true);
prime[0] = false;
prime[1] = false;
int m = (int) Math.sqrt(n);
for (int i = 2; i <= m; i++)
if (prime[i])
for (int k = i * i; k <= n; k += i)
prime[k] = false;
return prime;
输出是:
[11, 12, 14, 16, 21, 23, 25, 32, 38, 41, 49, 52, 56, 58, 61, 65, 83, 85, 94, 101, 102, 104, 106, 110, 111, 113, 119, 120, 131, 133, 137, 140, 146, 160, 164, 166, 173, 179, 191, 197, 199, 201, 203, 205, 210, 223, 229, 230, 232, 250, 289, 292, 298, 302, 308, 311, 313, 317, 320, 322, 331, 335, 337, 344, 346, 353, 355, 364, 368, 371, 373, 377, 379, 380, 386, 388, 397, 401, 409, 410, 416, 434, 436, 443, 449, 461, 463, 467, 476, 490, 494, 502, 506, 508, 520, 533, 535, 553, 559, 560, 566, 580, 595, 601, 605, 610, 614, 616, 634, 638, 641, 643, 647, 650, 656, 661, 665, 674, 683, 689, 698, 713, 719, 731, 733, 737, 739, 746, 764, 773, 779, 791, 793, 797, 803, 805, 829, 830, 836, 838, 850, 863, 869, 883, 892, 896, 904, 911, 917、919、922、928、937、940、944、955、968、971、973、977、982、986、991]
【讨论】:
不幸的是,您的算法在 (B - A) -> 10^18 处仍然很慢,因为它仍然是线性的 在我的实现中,我使用了 Eratosthenes 方法。那是微不足道的。要优化的主要是遍历所有数字并计算它们的数字之和,因为数字范围从 1 到 10^18。 素数的范围实际上要小得多 我同意这也较慢。也许这可以通过使用多台机器和 MapReduce 框架来加速?或者对于在单台机器上运行的程序,还有其他更快的解决方案吗? 不。没有并行性,只有一台机器。我相信秘密在于数字的属性。【参考方案7】:我没有仔细分析您当前的解决方案,但这可能会有所改进:
由于数字的顺序无关紧要,您应该检查所有可能的数字 0-9 长度为 1 到 18 的组合,跟踪数字的总和及其平方,并一次添加一个数字,使用之前计算的结果。
因此,如果您知道 12 位数字之和为 3,平方为 5,请查看数字 120, 121, 122... 等,并从 3 和 5 中为 12 计算它们的总和。
【讨论】:
整数分区算法就是这样做的。而且每个分区都被置换了。 等等,为什么需要置换?您应该能够使用公式计算排列数? 取 11 和是 2.. 110 和 101 也产生相同的和。 您只需要查看 11,然后查看 110,因为您可以在按顺序枚举时始终保持您的数字。如果您生成有序数字的组合,则无需同时查看 110 和 101。然后,一旦您知道 110 是好的,您将通过这 3 位数字的可能排列数来增加您的计数(小心排除带有前导 0 的那些)。【参考方案8】:有时最快的解决方案非常简单:
uint8_t precomputedBitField[] =
...
;
bool is_lucky(int number)
return precomputedBitField[number >> 8] & (1 << (number & 7));
只需修改现有代码即可生成“precomputedBitField”。
如果您担心大小,要覆盖从 0 到 999 的所有数字,它只需要 125 个字节,因此这种方法可能会比其他任何替代方法更小(而且速度更快)。
【讨论】:
与所有算法的复杂性相比,按位运算没有任何成本。此外,素数数组很容易放入处理器的缓存中,从中获取数字将比对数组执行操作更快 对不起。我无法理解这个解决方案。你能不能再解释一下。谢谢 这不是一个新的解决方案,而是改变现有的以使素数数组更好地适应内存。对于我们的情况,这并不重要【参考方案9】:我试图使用 Pierre 的枚举方法想出一个解决方案,但从未想出一种足够快的方法来计算排列。 OleGG的计数方法很聪明,需要pirate的优化才能让它足够快。我提出了一项小改进,以及一个解决严重问题的方法。
首先,改进:您不必逐个检查所有的和和平方和,检查 pirate 的 j 和 k 循环中的素数。您拥有(或可以轻松生成)素数列表。如果您使用其他变量来确定哪些素数在范围内,您可以逐步浏览适合 sum 和 squaresum 的素数列表。一个素数数组和一个查找表可以快速确定素数 >= 数字在哪个索引处是有帮助的。然而,这可能只是一个相当小的改进。
最大的问题在于 pirate 的 ans 缓存数组。它不是声称的 45MB;对于 64 位条目,它类似于 364MB。这超出了 C 和 Java 的(当前)允许的内存限制。通过去掉“l”维度,它可以减少到 37MB,这是不必要的,无论如何都会损害缓存性能。您真的对缓存 l + sum 和 l*l + squaresum 的计数感兴趣,而不是单独缓存 l、sum 和 squaresum。
【讨论】:
【参考方案10】:首先我要补充一点,可以通过筛子计算幸运数字,筛子的解释可以在这里找到:http://en.wikipedia.org/wiki/Lucky_number
因此您可以使用筛子确定数字来提高求解速度,
【讨论】:
我认为 sieve 生成的“幸运数字”不是我要寻找的“幸运数字”。 “如果一个数字的数字之和以及其数字的平方和是质数,则该数字称为幸运数字”以上是关于寻找幸运数字的算法的主要内容,如果未能解决你的问题,请参考以下文章