python 求小于给定数且因子数最多的数

先说原题目,应该是跟一个算法有关系,看了 CSDN 有几个 C 语言代码,可能原题是让用 C 语言编写的?题目如下:

e3e17bfee10742a3a5ae324f1083ded8.png

这个应该是题解 因数最多的数_c语言输出一个范围内因子数最多的数-CSDN博客,我就只放链接,不往文章里放代码了

原理和思路还是说一下,对于一个正整数 \(n\) ,若其素因子分解形式如下

$$n = p_1^{\alpha _1}p_2^{\alpha _2} \cdots p_r^{\alpha _r}$$

则 \(n\) 的因子个数为 \(\sigma _0(n)=(\alpha _1+1)(\alpha _2+1)\cdots (\alpha _r+1)\) ,说白了就是在 \(n\) 的所有的素数因子里挑选组合,每个素数因子 \(p_i\) 可选 \(0\) 到 \(\alpha _i\) 次,共 \(\alpha _i+1\) 种情况,将其相乘就是其因子的所有可能情况数了

于是我们可以通过构造一个因子数尽可能多的数来筛选排除,一般来说,其素因子越小越好,将较小的素因子分配的指数大一点。题解基本上就是这个思路

模仿着用 python 写了一段,也挺好写的,说实话用 python 确实香,不怕整数超限,像题解用 C 语言的话,\(n=10^{18}\) 就超限了

然后就是我看题解还有其他文章,都是只考虑因子数最多且本身最小的那个数,不过相同的因子个数可以对应不止一个原数,我还是想让这些原数全都展示出来。然后就是我不只关注因子个数与原数是什么,我还想知道每个原数的素因子分解情况,让它顺便展示出来,代码如下

n = eval(input())  # 如 1e20、10**20
# n < 1e20 时,15个素数足够使用,这里给20个
prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
mc = 0  # 最大因子个数
lst = []  # 所有解的素因子指数列表
ans = []  # 所有解的原数


def dfs(m, u=0, x=1, cnt=1, r=[]):  # m:每个素数的最大指数  u:当前使用的素数索引  x:当前原数  cnt:因子个数  r:指数列表
    '深度优先搜索(Depth-First Search)函数'
    global mc, lst, ans
    if cnt == mc: ans.append(x); lst.append(r)  # 添加到解集中
    if cnt > mc: mc = cnt; ans = [x]; lst = [r]  # 更新解集
    if u == 15: return
    for i in range(1, m + 1):  # 遍历每个素数的所有可能指数
        x *= prime[u]
        if x > n: break
        dfs(i, u + 1, x, cnt * (i + 1), r + [i])  # 递归调用,使用下一个素数


dfs(10)  # n < 1e20 时 m = 10 足够

# 遍历得到的解,尝试交换素数的指数来找到所有解
for num, s in zip(ans.copy(), lst.copy()):  # 将原数与指数列表对应组合
    p = s + [0, 0, 0]  # 末尾添加0,用于后续处理
    for j in range(len(s)):
        for k in range(1, 4):  # 考虑最多相差3个索引的素数
            if p[j] - p[j + k] == 1:  # 只考虑指数相差1的情况
                new = num // prime[j] * prime[j + k]  # 计算新的原数
                q = p.copy()
                q[j], q[j + k] = q[j + k], q[j]  # 交换指数
                if new < n:
                    while q[-1] == 0:  # 移除末尾的0
                        q.pop()
                    lst.append(q)
                    ans.append(new)

print(mc)
print(lst)
print(ans)

一般来说,解得的原数中,其较小的素因子的指数都不会小于其较大的,比如 \(12=2^2 \times 3\) 与 \(18=2\times 3^2\) 的因子个数相同,但 \(12\) 显然更小,更有可能成为解,不过实际情况也有例外。而代码中的 dfs 函数只考虑指数依次递减或相等的情况,如果修改 dfs 函数,允许较大素因数的指数大于较小的,哪怕只允许一次,也会降低其搜索的效率。因此我还是想分析已经获得的解,因为这些解的个数不会很多。对于已经得到的解,可以尝试通过交换其素因子的指数,来寻找同样符合条件的解

测试了大量的实际情况,发现在 \(n<10^{20}\) 的情况下,没有逃出以下这几个限制条件的

1. 最多交换两个素数一次

2. 两个素数的指数只能相差1

3. 两个素数的索引最多相差3

否则交换后的原数就会大于 \(n\) ,比如 \(n=10^{10}\) 时,有 \(6983776800=2^5×3^3×5^2×7×11×13×17×19\) 、\(9311702400=2^7×3^2×5^2×7×11×13×17×19\) 、\(9777287520=2^5×3^3×5×7^2×11×13×17×19\) 、\(9448639200=2^5×3^3×5^2×7×11×13×19×23\) 、\(8454045600=2^5×3^3×5^2×7×11×13×17×23\)  这 5 个解,其中第 3 个解是交换 5 与 7 的指数,第 4 个是交换 17 与 23,第 5 个是交换 19 与 23,都在上述的限制条件下

然后就比较好处理了,正如我上面的代码,用 for 循环处理 dfs 搜索后得到的解,注意这个解既包含原数,也包含其素因子的指数列表,以避免丢失信息。在每次循环中,只要找到一个交换指数后仍然满足条件的解,往解集里面补充就行了。而我在添加解集之前,先把指数列表末尾的 0 都删掉,这样看起来形式更统一 

验证一下运行时间合理性,有两种方法

一、使用 PyCharm 的分析功能

首先把前面的 n = eval(input()) 改为具体的值 n=1e20 ,避免输入的时间也计算在内

n = 1e20

 然后进行分析

可见当 \(n=10^{20}\) 时,dfs 函数一共调用了 13611 次,总用时在 20 毫秒左右,还是挺高效的

二、用 Pycharm 对 Jupyter 笔记本的支持功能

首先把前面的代码一股脑地全部封装起来,方便直接调用

def div(n):
    '''展示给定范围内的正整数中
    因子个数最大值、对应的所有素因子指数列表、对应的所有原数
    调用范例:div(1e20),无须再 print'''
    # n < 1e20 时,15个素数足够使用,这里给20个
    prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
    mc = 0  # 最大因子个数
    lst = []  # 所有解的素因子指数列表
    ans = []  # 所有解的原数

    def dfs(m, u=0, x=1, cnt=1, r=[]):  # m:每个素数的最大指数  u:当前使用的素数索引  x:当前原数  cnt:因子个数  r:指数列表
        '深度优先搜索(Depth-First Search)函数'
        nonlocal mc, lst, ans
        if cnt == mc: ans.append(x); lst.append(r)  # 添加到解集中
        if cnt > mc: mc = cnt; ans = [x]; lst = [r]  # 更新解集
        if u == 15: return
        for i in range(1, m + 1):  # 遍历每个素数的所有可能指数
            x *= prime[u]
            if x > n: break
            dfs(i, u + 1, x, cnt * (i + 1), r + [i])  # 递归调用,使用下一个素数

    dfs(10)  # n < 1e20 时 m = 10 足够

    # 遍历得到的解,尝试交换素数的指数来找到所有解
    for num, s in zip(ans.copy(), lst.copy()):  # 将原数与指数列表对应组合
        p = s + [0, 0, 0]  # 末尾添加0,用于后续处理
        for j in range(len(s)):
            for k in range(1, 4):  # 考虑最多相差3个索引的素数
                if p[j] - p[j + k] == 1:  # 只考虑指数相差1的情况
                    new = num // prime[j] * prime[j + k]  # 计算新的原数
                    q = p.copy()
                    q[j], q[j + k] = q[j + k], q[j]  # 交换指数
                    if new < n:
                        while q[-1] == 0:  # 移除末尾的0
                            q.pop()
                        lst.append(q)
                        ans.append(new)

    print(mc)
    print(lst)
    print(ans)

然后在 Jupyter 笔记本里面调用函数 

可见运行时间在几十毫秒以内,完全在合理时间范围内

作为一种笔记本环境,Jupyter 确实好用,可以帮我们一步步分析代码,还能保留运行结果

代码思路及原理讲完了,也验证了时间合理性,但都是我自己一个人验证(我让 AI 给我优化代码,实在不靠谱),可能会有遗漏(比如 \(n<10^{20}\) 时有漏解的,不过我验证了不下几百次,应该不会有)或优化不到位的地方,如果读者有什么问题或想法,可以在评论区提出来

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值