五位同学 - Anya、Dasha、Alla、Lisa 和 Nastya - 决定从他们的名字开始查找阶乘。
Anya 很快就找到了自己的名字:数字 16641 的阶乘以数字 11533 开头,这对应于用俄语字母表中的数字替换 Anya 名字的每个字母。
大傻是下一个:46978!以51261开头,对应Dasha这个名字。
然后阿拉找到了她的:323172!以 113131 开头,对应于名字 Alla。
甚至丽莎也成功了:266538!以 131091 开头,对应于名字 Lisa。纳斯秋哈有点不走运——要么滑雪板坏了,要么程序很糟糕。
我该如何帮助纳斯坚卡?
换句话说,您需要找到一个自然数,其十进制阶乘以数字 151192033 开头,对应于名字 Nastya。
当我们的首席数学家正在准备答案时,这里有一个有点愚蠢(由于适应
Numba)的代码,它使用加速Numba.njit在 6 秒内计算出所需的值。 (如果没有Numba达到 1 亿,它会在 1.5 分钟内完成脱粒,这仍然比用 Python 无限 -s 计算要好得多int。)确实,它对结果的绝对准确性留下了一些疑问,因为计算是在浮点数中完成的点数。在某些特殊情况下,...999999所需的结果可能出现在数字的末尾,或者可能找不到。尽管可以在最后一位(这是什么?)数字上加 1,然后修剪数字。好吧,你可以写得更通用,速度更快,但看起来这已经很正常了。结论:
数字总是被简化为 的形式
x.xxxxxx,这样稍后您就不必在比较之前计算需要乘以多少位数字。因此,输出具有相同的外观。PS 在纯Python中,我也显示了名称,但
Numba我很挑剔,我不喜欢原生Python类型,特别是我无法立即赢得字典。这就是为什么它如此笨拙。PPS
C++也许一切都会在一秒钟内计算出来。解决方案
让我们使用区间运算来计算阶乘。间隔由三个非负数指定
I(m, d, e) = [m·10 e , (m + d)·10 e ]。
整数n对应于区间I(n, 0, 0) = [n·10 0 , (n + 0)·10 0 ] = [n, n]。
将间隔乘以整数:I(m, d, e) f = I(m f, d f, e)。
将间隔四舍五入10 r。请注意
⌊ m / 10 r ⌋·10 r ≤ m
m + d ≤ ⌈ (m + d) / 10 r ⌉·10 r
它从哪里来?
I(m, d, e) ⊂ I(⌊ m / 10 r ⌋, ⌈ (m + d) / 10 r ⌉, e + r)。
需要四舍五入来限制尾数中的位数。如果尾数变得比某个阈值长,则对间隔进行四舍五入,尾数再次变短。
舍入的区间包含原来的区间。
该程序读取所需的阶乘前缀和尾数中的位数。然后,它将连续的阶乘计算为舍入间隔。例如尾数五位数的计算:
五位数足以计算100 的最高两位!。惊人的效率。
保证阶乘的精确值在区间内。在每次迭代中,都会检查间隔末尾的尾数前缀。如果两个前缀都与搜索到的前缀匹配,则找到答案。此外,还检查增量是否没有按顺序赶上尾数。如果发生这种情况,继续搜索就没有意义了:不够准确。
对于问题中的示例来说,二十个字符就足够了。
考试
让我们确保阶乘计算正确。这里有两个困难:阶乘本身的计算速度并不快,而且打印需要更长的时间。在 Python 中,打印长整数所需的时间是数字位数的二次方。为了解决这个困难,在打印之前,将阶乘除以 10 的幂,这样就剩下大约 20 位数字。
Nastin 阶乘大约需要一个半小时来检查,其余的不超过几秒钟:
主要思想:计算高位数字时,不需要低位数字。
我们需要第一个
n数字;在计算中我们将仅使用2n最高有效数字(假设这并不总是正确工作,但对于当前数据来说这是正确的):约 40 秒内完成。
str您可以通过将退出条件移至循环本身来减少循环中的函数调用次数:现在~28秒。
我实现了同样的事情,不是用
str,而是用十的幂除法并计算 throughmath.log10:〜26秒。有些参数只是不知从何而来,如果
i它变得太大,那就不太好