You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何优化算法以高效计算指定区间内可被给定除数集整除的非重复被除数数量?

最优算法实现方案

嗨,这个问题的核心痛点是避免重复计数,同时要处理超大区间(1e18)的高效计算,咱们可以用容斥原理+除数集精简的组合方案,绝对是时间最优的路子,下面一步步拆解:

1. 先给除数集合“瘦个身”——预处理精简

直接用原除数集计算的话,比如原集合有50个元素,子集数量是2^50,这完全是天文数字,根本算不完。所以第一步必须给D“减肥”:

  • 去重:把重复的除数删掉,比如D里有多个2,留一个就行。
  • 踢掉冗余元素:如果某个除数d能被集合里另一个除数d'整除,那d就是多余的——比如D里有2和4,能被4整除的数肯定能被2整除,留着4只会增加计算量,直接删掉。
  • 特殊情况秒处理:如果D里有1,那整个区间[A,B]的数都能被1整除,直接返回B - A + 1就行,不用往下走了。

2. 用容斥原理精准计数

精简后的除数集大小会非常小(比如1-50的所有数精简后只剩15个质数),这时候用容斥原理就完全可行了:
容斥的核心就是“加单个数的计数,减两个数公倍数的计数,加三个数公倍数的计数……”,以此抵消重复统计的部分。具体操作:

  • 把精简后的除数列表记为d_list,长度为m(最多15,2^15=32768个子集,完全能快速遍历)。
  • 位掩码遍历所有非空子集:比如用二进制数的每一位表示是否包含对应的除数,比如二进制101就表示取第0个和第2个除数。
  • 对每个子集,计算所有元素的最小公倍数(LCM)
    • 计算的时候要注意,如果中途LCM超过了B,直接跳过这个子集——区间里不可能有能被它整除的数,白费功夫。
    • 计算LCM用公式LCM(a,b) = a // GCD(a,b) * b,先除后乘,避免大整数溢出(比如Python的int天然支持超大数,其他语言可能要用到大整数类型)。
  • 算完LCM后,计算区间[A,B]里能被它整除的数的个数:count = (B // LCM) - ((A - 1) // LCM)
  • 根据子集的元素个数决定加减:子集元素个数是奇数就加count,偶数就减count(容斥的交替规则,用来抵消重复计数)。

3. 几个关键的优化小技巧

  • 提前终止LCM计算:比如计算子集的LCM时,刚算到前两个元素的LCM就已经超过B了,后面的元素不用再算,直接跳过这个子集。
  • 位掩码遍历高效又简洁:用整数循环就能遍历所有子集,代码写起来也清爽,比递归遍历子集快多了。
  • 精简除数集是核心:这一步直接把子集数量从天文数字降到几千级,是时间优化的重中之重。

拿示例验证下

就用你给的例子:区间[1,10],除数集{2,3,4}

  • 精简后,4能被2整除,所以只剩{2,3}
  • 遍历所有非空子集:
    • 子集{2}:LCM=2,count=10//2 - 0//2=5,加5 → 总数=5
    • 子集{3}:LCM=3,count=10//3 -0//3=3,加3 → 总数=8
    • 子集{2,3}:LCM=6,count=10//6 -0//6=1,减1 → 总数=7
  • 最终结果7,和实际的{2,3,4,6,8,9,10}完全一致,正确!

时间复杂度唠两句

预处理的时间可以忽略不计(最多50个元素,两两判断整除关系也就2500次操作);子集遍历最多32768次,每次计算LCM最多15步,总操作数也就几十万次,哪怕是1e18的区间,也能瞬间出结果,绝对是时间最优的方案。

内容的提问来源于stack exchange,提问作者Ster-BigData

火山引擎 最新活动