在知乎上看到了这样一个有趣的问题,以及很厉害的回答,实在是很有意思。这里就写一写我的解决这个问题的方法以及当时的心路历程吧。
头图出自 fasnakegod 大大的 清水吟,搭配的曲子是 DDBY 的 Cramped space,笛声真的很棒,搭配轻快的鼓组和旋律,给人一种很悠闲放松的感觉呢。希望你也喜欢~
问题介绍
如果您不想点开那个链接的话,这个问题实际上只有一行:求 2025! 从右向左起第一个不为0的数字是什么。这算是一道奥数题吧,同时也是某本书(具体数学)上的课后习题。
由于 2025! 几乎是没法用计算器简单计算验证的(即便知乎上有神人算出来了这个值),我们可以权当这个问题是在问,某个大数字 $n$ 的阶乘:$n!$ 在十进制表示下的,从右至左数第一个非零数字是几。
这个待求数字描述起来好麻烦。我们就称这个数字为 $A$ 好了。另外,我们稍微滥用一下符号,用 $A$ 来取出我们要的那个数字,比如数字 $12345000$ 的 $A$ 就记作 $A(12345000) = 5$ 了。另外我们为了方便讨论,把 $n!$ 的结果表示为 $a_k\dots a_3 a_2 a_1$,即给每一位上的数字都编个号。如 $120$ 的 $a_3 = 1$,$a_2 = 2$,$a_1 = 0$。
好了,我们现在开始吧,尝试解决这个问题。
第一次尝试:肯定得和质数有关,吧?
这个题肯定得有个通用算法,但是在发现这个通用解法前,我们还是手动尝试几个简单的值,观察下有没有什么规律吧。
不要 $0$,谢谢。
比如我们计算 $5! = 1\times 2\times 3\times 4\times 5 = 120$,那么我们要的 $A$ 就等于 2 了。这个结果里有一个0,它源自于 $2$ 和 $5$ 的乘积。这一定很重要!我们还知道 $4\times 25 = 100$,$8\times 125 = 1000$ 等。我们肯定在求 $A$ 时肯定不希望考虑这些 “没用” 的数字,因为它们的结果对我们要求的 $A$ 没有任何的影响。
我们更进一步,考虑上面几个乘积,实际上都是 $2$ 和 $5$ 的次幂相乘,或者说有几个 $2$ 和 $5$ 的配对。我们可以发现:如果阶乘在被质因数分解后,出现了若干 $2$ 和 $5$ 配对,那么这个配对就对最后的结果没有影响。
好,那我们就把阶乘拆成质因数们吧!然后拆掉里面的每个 $2-5$ 对儿,最后就只剩下最后的一堆结果,我们把它们乘起来看最后一位数字,肯定就是 $A$ 了……
对,对吗?算了,我们先算个简单的,我们让 $n = 10$ 看看结果吧。我们先找到质数们。10 以下的质数只有 $2, 3, 5, 7$ 四个,然后把 10 以下的数字们拆成质因数后统计它们的个数,得到:
质数 | 个数 |
---|---|
2 | 8 |
3 | 4 |
5 | 2 |
7 | 1 |
然后我们删掉俩 $2$-$5$ 对后把别的乘起来,得到结果是:$36288$ ,所以 $A = 8$
OMG 好麻烦,怎么就两对儿?这还只是 $10$ 以下的质数,$100$ 以下的质数有 25 个嘞。这要怎么搞?
其实我们应该只用算 $A$ 来着……
但是我们好像也不用算整个结果吧,得到 $A=8$ 就已经 OK 了的样子。而我们想得到 $A$ 好像也只需要关注每一次乘出来的结果的个位数就好?
我们看看这四个数字的次幂,它们的个位数都有什么特点:
质数 | 1次 | 2次 | 3次 | 4次 | 5次 |
---|---|---|---|---|---|
2 | 2 | 4 | 8 | 6 | 2 |
3 | 3 | 9 | 7 | 1 | 3 |
5 | 5 | 5 | 5 | 5 | 5 |
7 | 7 | 1 | 3 | 9 | 7 |
好像有点意思,它们的结果是循环的,且实际上只有 $2$-循环 和 $3$-循环两个循环。
等一下,我们好像还没考虑别的质数,比如什么 $11$, $13$,$17$, $19$ 等。然而,嘛,我们可以发现,任何大于 $10$ 的质数们的末位只会是 $1, 3,7, 9$ 四个数字,而它们又恰好都在 $3$-循环的样子。
可是,即便如此,我们要怎么数质数?这个方法强烈依赖把质数的个数数清楚这样的麻烦问题的解决。感觉还是不行……
第二次尝试:至少,确实我们只用管最后一位
重复的 $1$ 到 $9$,是否预示了什么!?
但是好消息也有,那就是我们锁定了 只管最后一位 的思路。假如我们只考虑最后一位数,我们又何必考虑什么质数什么的东西呢?比如我们考虑 $A(20!)$,那么我们就得计算 $1\times 2\times \dots \times 9 \times 10\times 11\times 12\times \dots\times 19\times 20$ 的 非0个位 们的乘积结果,也就是反复计算 $A(9!)$。在计算结束后,我们还得考虑乘上 $10$ 里面的 $1$,以及 $20$ 里的 $2$。
诶?好像我们计算 $A(n!)$ 可以简化为 计算小于 $n$ 每一个数对应的 $A$ ,然后计算它们的乘积的 $A$。写得更 数学 一点就是说:
$$ A(n!) = A(\prod_{i=1}^{n} i ) = A(\prod_{i=1}^{n} A(i)). $$如果这个是成立的,那我们的计算完全可以只关心每个数字所对应的 $A$,因为我们始终只想要那个非零的最后一位数字。我们甚至可以有这样的等式:
$$ A(A(xy))=A(A(x)A(y)). $$即任意两个数乘积所对应的 $A$ 等于两个数对应的 $A$ 相乘后再取 $A$。取两次 $A$ 的主要原因是为了规避可能存在的末位的零们。那么,照这个方法的话,也许我们可以重复计算很多次 $A(9!)$,其结果是 $8$;然后数清楚有多少个对应的 $1$-$9$,即有多少个 $8$ 相乘,取到它对应的 $A$,最后再乘上不够 $1$ 到 $9$ 的几个数字的阶乘对应的 $A$。当然,最最后再取一次 $A$,就是我们要求的结果了。
我们用 $A(20!)$ 来验证一下我们的想法吧。由上面的过程,我们可以看到个位上的 $1$ 到 $9$ 一共出现了两次 (即 $1$ 至 $9$ 和 $11$ 至 $19$)。 那么按照我们的算法, $20!$ 对应的 $A$ 那就是 $A(8\times 8\times 1\times 2) = 8$。
我算的对吗?经过计算器的暴力计算,我们得到它的结果是 $2432902008176640000$,则其 $A = 4$。
太棒了,我算错了。太坏了,怎么会这样!?
$5$ 怎么这么坏
上面一套分析,竟然结果是错的?!?到底是哪里出问题了?经过仔细的验算以及一点点点点的细心,可以发现罪魁祸首是个位的 $5$,因为每当 $5$ 出现时总会让结果出现一个 $0$ 然后向前进一位,或者说,乘以 $10$ 之后再除以 $2$。
在考虑到这点后,如果尝试把个位的 $5$ 从 $9!$ 抛掉的话,我们会发现结果的末位不会有 $0$。我们还很容易得出,事实上,如果我们抛去所有个位为 $5$ 或 $0$ 的数字的话,$n!$ 的末位就不会是0。那么我们好像可以重新组织一下这个乘积的样子,比如我们先计算个位不含有 $5$ 的数字们的乘积,对 $A(9)$ 来讲即 $4!$ 和 $9!/5!$,结果分别为 $24$ 和 $3024$。然后我们再把它们的个位相乘后乘 $5$ 或者除以 $2$,得到的结果就是我们需要的结果了。简单的计算即可知道答案是 $8$,没有问题。
嗯?!?等一下,这两个乘积的末位依旧是同一个数字 $4$!貌似这样四个一组的数字乘积的个位一定是 $4$ 的样子!?我们好像又可以采用刚刚的思路了。之前我们是 9 个数字为一组,这种方法里包括了 $5$ 这个捣蛋鬼所以失败了,那么这次我们就采用 4 个数字为一组。为了方便,我们就叫这个组为 $S$ 好了。
还是用 $20!$ 试一下,其中有 4 个 $S$,则我们得求 $ A(4^4) = 6$;里面有4个包含了 $5$ 为质因子的坏蛋,分别是 $1\times 5$,$2\times 5$,$3\times 5$,$4\times 5$,我们把它们乘起来,得到:
$$ \prod_{i=1}^{4} i \times 5^4 = 4! \times 5^4 = 15000 $$最后我们把它们俩乘起来得到…… 嗯?怎么是 $90000$ !?又是哪里出问题了?如果考虑到乘以 $5$ 在我们的问题中实际上相当于除以 $2$ 的话,我们发现:
$5$ 还会抢走别的 $2$,不行!
太坏了,实在是太坏了。还好我们还有招:$S$ 里一定有多出来的 $2$ 喂给白眼狼 $5$,我们只需要考虑喂出去多少 $2$ 给不够的 $5$ 来凑。我们也许不应该急着计算 $A(4^4)$,而是把外面的 $A$ 给去掉,因为只剩下一位数字的时候肯定不够质因子 $2$ 喂给多出来的 $5$ 的 (因为 $4$ 的幂次循环只有 $4$ 和 $6$)。经过这样的修正,我们可以得到:
有 4 组 $S$,则这四组的个位数乘积为 $4^4 = 256$。把这个结果乘上 $4! \times 5^4$,得到 $256\times 15000 = 3840000$ 再取 $A$,就能得到结果是 $4$。这下应该没问题了。我们来把这个过程规范地描述一遍吧。
要计算 $A(n!)$,首先我们要找出 $n$ 可以分出多少组 $S$。计算方法很简单,我们用带余除法即可。这样一来,我们就可以确定有多少个 $S$ 即多少个 $4$ 要相乘,以及剩下的余数是多少。我们记 $S$ 的组数为 $k$,记余数为 $r$。另外我们还可以知道剩下的含 $5$ 的数字都是什么,即 $5$ 的倍数都有谁。由于阶乘的特点,剩下的 $5$ 的倍数的乘积一定能写成 $k!\times 5^k$,而这里的 $k$ 正是前面 $S$ 的个数。
那么我们得到这样的式子:
$$ A(n!) = A(A(4!)^k\times k!\times 5^k\times r!) = A(4^k\times k!\times 5^k\times r!), $$注意到 $4^k \times 5^k = 20^k$,则 $A(4^k \times 5^k) = A(2^k)$,我们把上式简化为:
$$ A(n!) = A(4^k\times k!\times 5^k\times r!) = A(2^k \times k! \times r!) = A(A(2^k)\times A(r!)\times A(k!)). $$其中由于 $r$ 是一个小于 $5$ 的数字,它的阶乘特别好算,我们甚至可以打表,而 $2$ 的幂次的个位也是以 $2,4,8,6$ 进行循环的,我们可以把注意力完全放在 $A(k!)$ 上,而 $k$ 则是原数字 $n$ 缩小了四倍后的结果。接下来我们故伎重施来求解 $A(k!)$,得到的结果带回给原式后又会得到相似的模式,我们不断重复这个过程,就能递归地得到全部化简后的式子,而以这个方法得到的结果最后会由 $2$ 的幂次和余数们的阶乘的乘积构成,我们只需要求这个式子的 $A$ 即可,这是完全可以做到的。
我们总算得到了可用的算法了。太棒啦!然而,递归的算法总是在很精妙的同时给人以 “我能再进行简化” 的感觉。这里简化的重点应该在于 $k$。如果我们能提前把 $k!$ 解开,或者说在一开始就能有方法把 $n!$ 拆开成 $2$ 的幂次和一系列的 $r!$的话,就能不依赖递归的计算了。
第三次尝试:算法一定还有提升空间!
根据刚才的分析,很明显我们要首先得到我们得计算 $2$ 的多少次幂。而这个值又由 $S$ 组的数量控制。我们得给这个 $S$ 组一个比较明确的含义了,之前说什么 $4$ 个数字为一个 $S$ 组,这还是太模糊。我们所说的一个 $S$ 组应该是这样的连续数字组,它们的个位从 $1$ 到 $4$,或者从 $6$ 到 $9$ 为一组。$S$ 组有这样的特点,那就是它们均匀地分布在 $n!$ 中且数量极易统计,对于 $n!$ 的 $S$ 组,我们只需要把 $n$ 除以 $5$ 就能得到组数,而剩下的余数我们可以留作后用。另外每个 $S$ 组组内的乘积对应的 $A$ 值一定是 $4$。这是非常重要的特点,也就是这一点能让我们进行简化计算。
由于在进行上述的算法计算时,我们必须不断地计算 $A(k!)$ 的值。而这也就意味着我们必须多次计算每次出现的 $S$ 组的数量。有什么办法能让 $k!$ 把 $S$ 组的数量一次全吐出来呢?
假如 $5$ 不是坏蛋的话?
那假如 $5$ 不是坏蛋,乘以 $5$ 不会让后面多 $0$ 进而影响结果,那样的话不就只有整 10 数会影响最后的结果了?这也许能给我们一些计算到底统共有多少组 $S$ 的启示。
我们试试用那个本来错了的方法进行分组吧。按 $A(n)$ 从 1 到 9 来给 $n!$ 中的所有因数进行分组。假如我们凑齐了 9 个数字,让它们的 A 正好遍历 1 到 9, 我们就记这样一个组为 $S_{10}$。根据我们老早提过的记号,我们把 $n$ 记成 $\dots a_3 a_2 a_1$ 的话,那么我们有:$\dots a_3 a_2 0$ 个 个位数不为0 的组,有 $\dots a_300$ 个 个位数为 0,但十位数不为 0 的组,有 $\dots a_4000$ 个 个、十位数为 0 但百位不为 0 的数 ……
注意到上面的分法下每个组在取 $A$ 后都是我们要的 $S_{10}$,我们就能很方便的计算 $S_{10}$ 的个数。假如 $n = 1234$,那么对应的我们要的 $S_{10}$ 的数目就是 $123+12+1 = 136$ 个了。这里值得注意的是一个边界情况,即假如我们的数字是 $9$,$90$,$990$ 这样的情况下,我们的 $S_{10}$ 实际上是 $0+1$,$9 + 1 = 10$ ,$99+9+1 = 109$ 组。如果是单纯统计 $S_{10}$ 组的话,为了解决这个纰漏,我们可以考虑检测第一位数字是否是 $9$,如果是的话就额外加上 1, 不是的话就说明没凑够所以不加。然而我们并不是单纯统计 $S_{10}$ 组,因此我们干脆不管这个边界情况,这一点我们后面再多做讨论。
能观察到,在 10 进制下我们对 $A(n)$ 遍历 1 到 9 的分组是极为自然的过程。究其原因,这样的便利性是来源于十进制的表达方式。那么,如果要统计 五个一组 的情况呢?这给了我们尝试 5 进制的理由。我们来试试吧。
5进制下统计所有的 $S$ 组
下面我们把 10 进制的数字直接简单地表示出来,而 5 进制的数字则会有个 5 的下标。我们还是把 5 进制下的数字表达为 $\dots a_3 a_2 a_1$。这样一来,10进制下的 $1$ 到 $4$ 就是 5进制下的 $1_5$ 到 $4_5$, 而 $6$ 到 $9$ 就是 5 进制下的 $11_5$ 到 $14_5$ 了。那 十进制下的 $30$,在 5 进制下的表示是什么呢?由于 $30 = 1\times 5^2 + 1\times 5^1 + 0\times 5^0$,其 5 进制表示则为 $110_5$。
那么 $30!$ 里统共有多少 $S$ 组呢?我们类比上面的做法,我们首先有 $11_5$ 组个位不为 0 的组,其次有 $1_5$ 个十位不为 0 而个位为 0 的组,一共就是 $12_5$ 即 $7$ 组。如果我们手动统计的话,$30$ 首先除以 $5$ 得到 $6$,另外由于我们的算法会出现一个 $6!$,其 $S$ 组只有一个,这样一来,$30!$ 里一共应该有 $6+1=7$ 个 $S$ 组,结果和前面的算法是一致的。
我们再来看看 $100!$ 里有多少 $S$ 组。写为 5 进制后它是 $400_5$,那么他就有 $40_5 + 4_5 = 44_5 = 24$ 个 $S$ 组了。而使用古法统计,可以得到其首先是 $20$ 个 $S$ 组,接下来对 $20!$ 而言一共有 $4$ 个 $S$ 组,则一共是有 $24$ 个。结果也是一致的。
太棒了,我们现在能成功分析出一个数字拥有的 $S$ 组了。然后呢?
还需要统计余数
在分析出一共到底有多少 $S$ 组后,我们就知道了 $A(n!)$ 计算式里 $2^k$ 中的 $k$ 了。然而,在拆出来 $S$ 组的过程中,我们还会得到一系列的余数。我们得想办法把这些余数留下来做计算。要怎么做呢?
我们在十进制下每次除以 $10$ 的时候会得到商和余数,其余数就是右侧最后的一个数字,而商则是去掉最右侧一位数字后得到的剩余部分。这个规则对于 5 进制,甚至于任何进制,都是成立的。这样一来我们就很快能得到所有的余数了,就是它的每一位数字。比如有 5 进制数 $131423_5$,其所有的余数就是 $1,3,1,4,2,3$ 了。这里我们考虑了最左边的 $1$,原因有二:如果只考虑直接的余数的话,最后还是会碰到要乘以最左边一位数字的阶乘;另外,在做进制转换的过程中,我们是要除到结果为 $0$ 的,而最后一次的余数正是最左边的一位数字。
那么这样一来,我们的算法就完善了。我们先统计出 $S$ 组总共的个数,得到 $k$,然后用 $k$ 模除 $4$ 得到余数,用这个余数 $r$ 计算 $2^r$ 后再乘上每一位余数的阶乘,最后取这个结果的 $A$ 即得到我们要求的 $A$ 了。
我们尝试在新的算法下计算 $A(13!)$。它的 5 进制表达为 $23_5$,则一共有 $2_5 = 2$ 个 $S$ 组。那么它对应的 $A$ 就有 $A(13!) = A(4^2 \times 5^2 \times 2! \times 3!) = 8$,容易验算,这个结果是没问题的。
我们再尝试计算一下 $A(20!)$。我们把 $20$ 写为 5 进制后得到 $40_5$,则我们有 $4_5$ 即 $4$ 个 $S$ 组。则我们要的 $A$ 就可以是 $A(20!) = A(A(2^4)\times 4!) = 4$,和我们上面的结果是一样的。我们再试试求 $A(63!)$,由于 $63 = 223_5$,则一共有 $24_5 = 14$ 组 $S$。此时我们要的 $A(63!)$ 就是 $A(A(2^14) \times 3! \times 2! \times 2!) = 6$。我们用 Python 算一下这个值,结果是
$$1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000$$它的 $A$ 和我们的要求是一样的。好耶!我们成功地找到了一个可行的算法!那么既然是一个算法,我们是不是能写成程序呢?
用 Python 实现一下吧~
我们还是选择我们亲爱的 Python。虽然说是胶水语言,但是真的很好用,特别是在处理这种东西的时候,有很多已经内置了的方程。更不必提在 3.13 版本后 Python 的交互式界面好用了很多:支持自动缩进,支持 exit
退出等等。真的很不错。
不多废话了。我们开始实现这个算法吧。首先自然是要把 10 进制数字转换为 5 进制。另外,待会儿我们还需要把 5 进制转换回 10 进制,所以一起实现了吧。为了某种 “广泛性“,我们干脆让这样的进制转换支持 任意数字为底 好了。
进制转换
注意到每一位数字就是除法的余数,我们可以让数字依次除以底数,最后把它们反方向拼接起来就可以了。具体实现如下:
1def change_base(n:int, base):
2 """ Converts a decimal number to its representation in a given base. """
3 if n == 0:
4 return "0"
5
6 digits = []
7 while n:
8 digits.append(int(n % base))
9 n //= base
10 return ''.join(str(x) for x in digits[::-1])
值得注意的是我们这里返回的是字符串,而非数字。因为数字自动是以 10 为底的。为避免我们不想要的运算,我们还是使用字符串的稳妥一些。
另外我们要把 5 进制数字(的字符串)转换回 10 进制。这点非常简单,实现如下:
1def to_decimal(s:str, base):
2 """ Converts a number in string representation from a given base to decimal. """
3 n = 0
4 length = len(s)
5 for i, digit in enumerate(s):
6 n += int(digit) * (base ** (length - i - 1))
7 return n
这样一来,我们就能自由地在 10 进制和 5 进制之间转换了。当然,为了方便,我们定义 to_penta
函数:
1def to_penta(s:str):
2 return change_base(s,5)
统计 $S$ 组个数
我们遇到的第一个比较难的点应该在于如何统计 $S$ 组的个数。我们之前是计算的 5 进制加法之后转换回 10 进制的。然而这样的算法不太适合计算机:它不熟悉怎么计算奇怪进制的加法。好消息是,对于加法而言,我们先加起来后进行进制转换,和先进行进制转换然后加起来是一样的效果。这里我就不证明这一点了。在知道这一点之后,我们就可以很简单地得到 $S$ 组的个数:
1def num_S(s:str):
2 acu = 0
3 for i in range(len(s)):
4 d = (s[:len(s)-i-1])
5 acu = acu + to_decimal(d,5)
6 return acu
然后我们还需要根据这个值来决定 $2$ 的幂次的个位结果。Python 提供了好用的 divmod
函数方便我们处理这个结果。待会儿我们就用它。而接下来的问题则是把所有的数位的阶乘都乘起来。
处理阶乘们
在处理阶乘前,我们可以观察到这样一个神奇的现象:每一位上的数字只有从 $0$ 到 $4$ 五种结果,其中的 $0,1$ 的阶乘都是 $1$, 因此可以不考虑进去;$3!=6$ 的结果尤为特殊,因为它乘以偶数后取 $A$ 都是得到它本身,即若 $x\in \{0,2,4,6,8\}$,则 $A(3!\times x) = x$,然而又因为这个余数肯定得乘上前面 $2$ 的次幂,所以即便在余数中只有 $1$ 和 $3$,它的结果是 $6$, 随后又会被前面 $2$ 的次幂吸收。因此,我们可以不考虑余数中的奇数们,只考虑它们里面的偶数。再之后,$A(2!) = 2$,$A(4!) = 4$,我们可以把功夫全放在这两个数字上。
1def res(s:str):
2 result = 1
3 for dig in s:
4 if int(dig)%2==0 and dig != '0':
5 result *= int(dig)
6 return result
组合起来完成计算
最后,我们只需要把上面的几个函数组合一下就好了。
1def last_nonzero_digit(n:int):
2 penta_n = to_penta(n)
3 k = num_S(penta_n)
4 resudal_4 = divmod(k,4)[1]
5 A_pow_2 = 6 if resudal_4 == 0 else 2**resudal_4
6 ress = res(penta_n)
7 result = str(int (A_pow_2 * ress))[-1]
8 return result
好,现在只需要运行这个脚本,就能解决我们一开始拿到的问题了。我们要求的是 $2025$,那么 $A(2025!)$ 经过计算得到的结果就是:$2$!太棒了。这个算法还挺快的,几乎是无感计算诶。我试了一下,在我自己的 PC 上进行计算,算 $A(2^{5000}!)$ 大概用了两秒就得到了结果。
然而,我们的结果对吗?应该是有个答案吧,答案怎么做的呢?
终章:神秘的算法,怎么能这么快?
我们引入这个问题的时候,就说过知乎上有人发过一个很厉害的回答。这个算法令人惊讶的简单,不需要算若干次麻烦的除法,只需要算一次模除以 $4$ 就可以了。这个算法是这样的:
首先写成 5 进制,然后把每一位偶数加起来得到一个结果 $t$,然后把每一位和自己的位数(从0开始)相乘后相加得到 $x$,最后计算一个判别式 $y = (x+t/2) \mod 4$, 如果 $y = 0$ 则说明结果是 $6$,而剩下的情况则是 $2^{y}$ 即可。这个算法写成 Python 程序则为 (借用上面的转 5 进制算法)
1def quick_method(n):
2 s = to_penta(n)
3 t = 0
4 x = 0
5 i = len(s)-1 # index
6 for digit in s:
7 d = int(digit)
8 if d % 2 == 0:
9 t = t + d
10 x = x + i*d
11 i = i-1
12
13 z = (x + t/2) % 4
14
15 result = 6 if z==0 else int(2**z)
16 return result
这个算法太简洁了…… 对它的解释写在 这篇上古网页里。我实在是燃尽了,看不下去了。不过我认为其基本思路和我的算法应该是差不多的。它应该在求 $S$ 组这一步做出了很大的简化,并且把对余数的处理想办法捏进一个加和里。我也不知道他是怎么做到的。不过这个算法肯定是快的多的,因为它计算 $A(2^{5000}!)$ 是瞬间计算出来的。毕竟,时间复杂度在这里摆着……
也许某一天我会回来看这个算法的具体实现是怎么做到的吧!希望我会记得回来看。暂且就到这里吧,这个问题还伤了我不少脑细胞来着。
后记
其实,这个问题的解决并非一帆风顺。一开始我是在百无聊赖的状态下看到这个问题的,一下子就被吸引住了。这个问题实在是很有趣,而我一开始的思路,正如上面那样,尝试了质因数分解和一些有的没的,手动计算了 $A(20!)$ 来尝试寻找规律之类。然而,那天赶着吃饭,在发现可以以 9 个数字为一组进行操作之后就不再细想了。其实 9 个数字为一组的做法是错误的,错误原因直到后来我已经动笔开始写这篇文章的时候,我才后知后觉。好在很快意识到了问题,把 $5$ 这个绊脚石从脚边踢开后就能很好地进行计算了。
事实上,我在计算时一直在用最后给出的这个 quick_method
做结果对照。令人欣喜的是,结果是没问题的,我的算法设计顶住了 没有答案 的压力。毕竟,从最后的这个答案上,能得到的有效信息几乎只有 “记得使用 5 进制”。很难反推出来的啦,这套算法。当然,关于这个问题,我的终极目标当然是吃透这个算法究竟是怎么生效的。不过这也已经是后话了。
很明显这个问题是和数论强相关的,尤其和取模运算有很大的关系。然而,这里并没有深究,主要原因一个在于进行进制转换已经很麻烦了,没必要介绍太多数论的内容, 另一个也是我自己的问题:我不会数论,我讲个毛呀。因此,本着有啥写啥,用啥写啥的精神,最后只写出来这么个半吊子。希望看到这篇文章的你感觉还算有点意思吧。
还有就是要感谢 [柴(oneis2much)] 佬的细心审稿。谢谢你!
那么最后,一如既往地,祝您身心健康,工作顺利,生活愉快。