一尘不染

如何找到任何整数的乘法分区?

algorithm

我正在寻找一种有效的算法来计算任何给定整数的乘法分区。例如,此类分区的数量为12,为4,即

12 = 12 x 1 = 4 x 3 = 2 x 2 x 3 = 2 x 6

我已经阅读了维基百科上的文章,但这并没有真正给我生成分区的算法(实际上,它只是在谈论这样的分区的数量,即使这对我来说也不是很清楚!)

我要解决的问题要求我为非常大的数字(>
10亿个)计算乘法分区,因此我试图为此提出一种动态编程方法(以便找到较小数量的所有可能分区可以当较小的数字本身就是较大的数字的因素时可以重新使用),但到目前为止,我不知道从哪里开始!

任何想法/提示都将不胜感激-这不是一个作业问题,只是我要解决的问题,因为它 看起来是 如此有趣!


阅读 218

收藏
2020-07-28

共1个答案

一尘不染

当然,首先要做的是找到数字的素因式分解,如glowcoder所说。说

n = p^a * q^b * r^c * ...

然后

  1. 找到…的乘法分区 m = n / p^a
  2. 对于0 <= k <= a,找到的乘法分区p^k,等于找到的加法分区k
  3. 对于的每个乘法分区m,找到在a-k因子p之间分配因子的所有不同方法
  4. 合并2.和3.的结果

将乘法分区视为(除数,多重性)对的列表(或集合)以避免生成重复是很方便的。

我用Haskell编写了代码,因为它是我所知道的最方便,最简洁的语言:

module MultiPart (multiplicativePartitions) where

import Data.List (sort)
import Math.NumberTheory.Primes (factorise)
import Control.Arrow (first)

multiplicativePartitions :: Integer -> [[Integer]]
multiplicativePartitions n
    | n < 1     = []
    | n == 1    = [[]]
    | otherwise = map ((>>= uncurry (flip replicate)) . sort) . pfPartitions $ factorise n

additivePartitions :: Int -> [[(Int,Int)]]
additivePartitions 0 = [[]]
additivePartitions n
    | n < 0     = []
    | otherwise = aParts n n
      where
        aParts :: Int -> Int -> [[(Int,Int)]]
        aParts 0 _ = [[]]
        aParts 1 m = [[(1,m)]]
        aParts k m = withK ++ aParts (k-1) m
          where
            withK = do
                let q = m `quot` k
                j <- [q,q-1 .. 1]
                [(k,j):prt | let r = m - j*k, prt <- aParts (min (k-1) r) r]

countedPartitions :: Int -> Int -> [[(Int,Int)]]
countedPartitions 0     count = [[(0,count)]]
countedPartitions quant count = cbParts quant quant count
  where
    prep _ 0 = id
    prep m j = ((m,j):)
    cbParts :: Int -> Int -> Int -> [[(Int,Int)]]
    cbParts q 0 c
        | q == 0    = if c == 0 then [[]] else [[(0,c)]]
        | otherwise = error "Oops"
    cbParts q 1 c
        | c < q     = []        -- should never happen
        | c == q    = [[(1,c)]]
        | otherwise = [[(1,q),(0,c-q)]]
    cbParts q m c = do
        let lo = max 0 $ q - c*(m-1)
            hi = q `quot` m
        j <- [lo .. hi]
        let r = q - j*m
            m' = min (m-1) r
        map (prep m j) $ cbParts r m' (c-j)

primePowerPartitions :: Integer -> Int -> [[(Integer,Int)]]
primePowerPartitions p e = map (map (first (p^))) $ additivePartitions e

distOne :: Integer -> Int -> Integer -> Int -> [[(Integer,Int)]]
distOne _ 0 d k = [[(d,k)]]
distOne p e d k = do
    cap <- countedPartitions e k
    return $ [(p^i*d,m) | (i,m) <- cap]

distribute :: Integer -> Int -> [(Integer,Int)] -> [[(Integer,Int)]]
distribute _ 0 xs = [xs]
distribute p e [(d,k)] = distOne p e d k
distribute p e ((d,k):dks) = do
    j <- [0 .. e]
    dps <- distOne p j d k
    ys <- distribute p (e-j) dks
    return $ dps ++ ys
distribute _ _ [] = []

pfPartitions :: [(Integer,Int)] -> [[(Integer,Int)]]
pfPartitions [] = [[]]
pfPartitions [(p,e)] = primePowerPartitions p e
pfPartitions ((p,e):pps) = do
    cop <- pfPartitions pps
    k <- [0 .. e]
    ppp <- primePowerPartitions p k
    mix <- distribute p (e-k) cop
    return (ppp ++ mix)

它不是特别优化的,但是可以完成工作。

一些时间和结果:

Prelude MultiPart> length $ multiplicativePartitions $ 10^10
59521
(0.03 secs, 53535264 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^11
151958
(0.11 secs, 125850200 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^12
379693
(0.26 secs, 296844616 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 10]
70520
(0.07 secs, 72786128 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 11]
425240
(0.36 secs, 460094808 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 12]
2787810
(2.06 secs, 2572962320 bytes)

10^k当然是特别容易,因为只有两个素数涉及(但无平方数仍比较容易),该阶乘很慢更早。我认为,通过精心组织顺序和选择比列表更好的数据结构,有很多收获(可能应该按指数对主要因子进行排序,但是我不知道应该从最高指数开始还是要从最高指数开始)。最低的)。

2020-07-28