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

问题描述 投票:13回答:3

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

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

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

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

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

algorithm combinatorics discrete-mathematics divide-and-conquer
3个回答
5
投票

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


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));
    }
}

0
投票

为什么你没有找到可以划分数字的所有数字,然后你找到乘法将加到数字上的数字的排列?

找到可以划分数字的所有数字都需要O(n)。

然后你可以置换这个集合来找到所有可能的集合,这个集合的乘法将给你数字。

一旦找到除去原始数字的所有可能数字的集合,那么您可以对它们进行动态编程,以找到相乘的数字集合,它们将为您提供原始数字。

© www.soinside.com 2019 - 2024. All rights reserved.