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

时间:2011-12-19 07:27:23

标签: algorithm combinatorics discrete-mathematics divide-and-conquer

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

12 = 12×1 = 4×3 = 2×2×3 = 2×6

我已经为此阅读了wikipedia article,但这并没有真正给我一个生成分区的算法(它只讨论了这些分区的数量,说实话,即使这不是我很清楚!)。

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

任何想法/提示都会受到赞赏 - 这不是一个家庭作业问题,只是我试图解决的问题,因为似乎如此有趣!

2 个答案:

答案 0 :(得分:6)

我要做的第一件事就是得到数字的素数分解。

从那里,我可以对每个因子子集进行排列,乘以该迭代的剩余因子。

所以,如果你拿一个像24这样的数字,你就得到了

2 * 2 * 2 * 3 // prime factorization
a   b   c   d
// round 1
2 * (2 * 2 * 3) a * bcd
2 * (2 * 2 * 3) b * acd (removed for being dup)
2 * (2 * 2 * 3) c * abd (removed for being dup)
3 * (2 * 2 * 2) d * abc

对所有“轮次”重复(舍入是乘法的第一个数中的因子数),在它们出现时删除重复。

所以你最终会得到像

这样的东西
// assume we have the prime factorization 
// and a partition set to add to
for(int i = 1; i < factors.size; i++) {
    for(List<int> subset : factors.permutate(2)) {
        List<int> otherSubset = factors.copy().remove(subset);
        int subsetTotal = 1;
        for(int p : subset) subsetTotal *= p;
        int otherSubsetTotal = 1;
        for(int p : otherSubset) otherSubsetTotal *= p;
        // assume your partition excludes if it's a duplicate
        partition.add(new FactorSet(subsetTotal,otherSubsetTotal));
    }
}

答案 1 :(得分:5)

当然,首先要做的是找到数字的素数因子化,就像glowcoder说的那样。说

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

然后

  1. 找到m = n / p^a
  2. 的乘法分区
  3. for 0 <= k <= a,找到p^k的乘法分区,相当于找到k的附加分区
  4. 对于m的每个乘法分区,找到在因子中分发a-k因子p的所有不同方法
  5. 结合2.和3的结果。
  6. 将乘法分区视为(除数,多重)对的列表(或集合)是很方便的,以避免产生重复。

    我已经在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当然特别容易,因为只涉及两个素数(但是平方数字仍然更容易),因子提前变慢。我认为通过仔细组织顺序和选择比列表更好的数据结构,可以获得相当多的东西(可能应该按指数对主要因素进行排序,但我不知道是否应该从最高指数开始或者最低的。)