0254 最小阶乘和
* * * *
拉格朗日计划
* * * *
最小阶乘和

记$f(n)$为$n$各位数字的阶乘和。例如,$f(342)=3!+4!+2!=32$。

记$sf(n)$为$f(n)$的各位数字和,例如$sf(342)=3+2=5$。

记$g(i)$为满足$sf(n)=i$的最小n值,在上例中尽管$sf(342)=5$,但$sf(25)$也等于5(因为i$f(25)=2!+5!=122$),因此$g(5)$不超过25,事实上可以验证$g(5)=25$。

记$sg(i)$为$g(i)$的各位数字和,例如$sg(5)=2+5=7$。

此外,可以验证$g(20)=267$,以及对于$\sum_{i=1}^{20}sg(i)=156$。

求$\sum_{i=1}^{150}sg(i)$。

本题难度:



解答

首先注意到任意自然数n都可以写成另一个自然数m各位数字的阶乘和,比如取连续n个1就是一种写法。

记$ds(m)$为m中各位数字的和,$fs(m)$为m中各位数字的阶乘和,则$fs(m)$和$ds(m)$都与m中数字的顺序无关,因此当$fs(m)$一定时,位数越少且同等位数下字典序越小的m越能满足要求。

从而对固定的$n=fs(m)$,构造最小m的方法是找到最大的、满足$k!$不超过n的k,记录n模$k!$的商和余数$q_k,r_k$,再将n更新为$r_k$并重复这一过程直到余数为0(因为$1!=1$,所以最后总能达到余数为0的状态),那么m就等于连续$q_1$个1,连续$q_2$个2。。。连续$q_k$个k相连。

例如$122=1\times 5!+1\cdot 2!$,因此满足$fs(m)=122$的最小m就是1个2后接1个5,即25。

遍历1到$10^7$之间的n,用上述方法计算最小的m并更新$g(ds(n))$,如此可计算出$g(1)$到$g(62)$。而对$i=63$到$i=150$之间的数,m,n增长都非常快,无法继续遍历。

此时可以观察到的规律是例如$i=63,64,65,\ldots$时$fs(m)$分别等于$9999999, 19999999, 29999999,\ldots$,从而有贪心推测 $$fs(m)=(i\bmod 9+1)\cdot (10^{\lfloor i/9\rfloor})-1,$$ 这一步是启发式的,缺乏直接的论证,这也是本题的主要难度所在,需要识别出在$i=63$处作分割并归纳出$i\ge63$时的规律。

在实现中还需要将不超过$9!$的结果都作缓存再直接求和(否则在$i\ge63$时所涉及的整数m仍然非常巨大,尝试生成这样的m并根据数位运行仍会十分缓慢)。

最终结果是$8184523820510$(其中前62项的和只有3077)。

注:以下代码中还输出了第一部分(即$i<63$时)遍历中的进度信息。

import math

ds=lambda n:sum(int(d) for d in str(n))

infty=0
target=10000000
bound=math.factorial(9)
cutoff=63

f=[math.factorial(i) for i in range(10)]
c=[infty]*bound
g=[infty]*cutoff

k=1
for n in range(1,target):
    while k<9 and f[k+1]<=n:
        k+=1
    q,r=n/f[k],n%f[k]
    m=int(str(k)*q) if r==0 else int(str(c[r])+str(k)*q)
    if n<bound:
        c[n]=min(c[n],m) if c[n]!=infty else m
    s=ds(n)
    if s<cutoff:
        g[s]=min(g[s],m) if g[s]!=infty else m
    if n%1000000==0:print n/1000000,"/10 completed."

print sum(ds(n) for n in g[1:])+sum(9*(n/f[9])+ds(c[n%f[9]]) for n in [(i%9+1)*(10**(i/9))-1 for i in range(63,151)])