15

I'm looking for an efficient algorithm for computing the multiplicative partitions for any given integer. For example, the number of such partitions for 12 is 4, which are

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

I've read the wikipedia article for this, but that doesn't really give me an algorithm for generating the partitions (it only talks about the number of such partitions, and to be honest, even that is not very clear to me!).

The problem I'm looking at requires me to compute multiplicative partitions for very large numbers (> 1 billion), so I was trying to come up with a dynamic programming approach for it (so that finding all possible partitions for a smaller number can be re-used when that smaller number is itself a factor of a bigger number), but so far, I don't know where to begin!

Any ideas/hints would be appreciated - this is not a homework problem, merely something I'm trying to solve because it seems so interesting!

TCSGrad
  • 11,898
  • 14
  • 49
  • 70
  • With close votes, there ideally should be some sort of an explanation as to why you think this needs to be closed, so that the OP can rectify his/her mistakes (if any). Can the lone close voter please speak up ? – TCSGrad Dec 19 '11 at 10:14
  • Close votes without any explanations - always loved those! – TCSGrad Dec 20 '11 at 07:08
  • I cast a close vote in error. My apologies. – Mr.Wizard Dec 22 '11 at 09:10
  • @Mods, is there any way to redact close votes cast in error? – TCSGrad Dec 22 '11 at 11:22
  • shan23, it really should not pose a problem; it would take a few more votes for this question to be closed. If that does happen, I will cast a reopen vote ASAP. – Mr.Wizard Dec 22 '11 at 12:06

3 Answers3

6

The first thing I would do is get the prime factorization of the number.

From there, I can make a permutation of each subset of the factors, multiplied by the remaining factors at that iteration.

So if you take a number like 24, you get

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

Repeat for all "rounds" (round being the number of factors in the first number of the multiplication), removing duplicates as they come up.

So you end up with something like

// 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));
    }
}
corsiKa
  • 81,495
  • 25
  • 153
  • 204
  • permutation of the numbers that multiplication will add up to the original number. – DarthVader Dec 19 '11 at 07:35
  • (permutation? combintion? I forget the proper word) it should be permutation. – DarthVader Dec 19 '11 at 07:46
  • @glowcoder: Some issues - for a sufficiently large number which has a lot of prime factors, much of the work would be done in identifying and removing duplicate partitions. I was looking for a way to get past that during generation itself. Also, what does factors.permutate(2) do ? I didn't find any API in STL corresponding to that, hence was wondering about the significance of the "2" parameter. – TCSGrad Dec 19 '11 at 09:55
  • @shan23 - there are some optimizations you can make to make it less damaging, but it is an inherently expensive operation as it is. The `permutate(2)` is a typo - it should be `permutate(i)` and no I don't believe it's in any STL library. It is pseudocode for a function that would return a list of list of values, each of size `i`, complete over all possible sublists. – corsiKa Dec 19 '11 at 13:19
5

Of course, the first thing to do is find the prime factorisation of the number, like glowcoder said. Say

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

Then

  1. find the multiplicative partitions of m = n / p^a
  2. for 0 <= k <= a, find the multiplicative partitions of p^k, which is equivalent to finding the additive partitions of k
  3. for each multiplicative partition of m, find all distinct ways to distribute a-k factors p among the factors
  4. combine results of 2. and 3.

It is convenient to treat the multiplicative partitions as lists (or sets) of (divisor, multiplicity) pairs to avoid producing duplicates.

I've written the code in Haskell because it's the most convenient and concise of the languages I know for this sort of thing:

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)

It's not particularly optimised, but it does the job.

Some times and results:

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)

The 10^k are of course particularly easy because there are only two primes involved (but squarefree numbers are still easier), the factorials get slow earlier. I think by careful organisation of the order and choice of better data structures than lists, there's quite a bit to be gained (probably one should sort the prime factors by exponent, but I don't know whether one should start with the highest exponents or the lowest).

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • I don't know Haskell, but I assume you've run your code - I was interested in knowing what kind of time does it take for large numbers (say ~10,000,000,000)? It would give me an idea of what to expect when I finally code my solution in C++... – TCSGrad Dec 21 '11 at 07:28
  • @shan23 I've added some timings. As a wild guess, a factor of 10 improvement doesn't look impossible. – Daniel Fischer Dec 21 '11 at 08:03
  • Thats a really great answer (with the timings) - let me try out on C++ over the weekend, and see if it gets better. Also, a related query - how would one utilize the partitions for $n$ when calculating the partitions for a bigger number, one of whose factors is $n$? I'm looking to get the partitions of a range of numbers n...m, so this would be particularly useful to me if I can figure out a way for that! – TCSGrad Dec 21 '11 at 17:27
  • Since the shape of the partitions depends only on the exponents in the prime factorisation, we can also reuse the partitions of `n` to generate the partitions of `m` if `n` is not a divisor of `m`, all we need is a suitable structure of the prime factorisation. For example, `12 = 2^2 * 3^1`, so we can reuse the partitions of 12 to find the partitions of `90 = 2^1 * 3^2 * 5^1`. We just have to exchange 2 and 3 in the partitions of 12 to get the partitions of 18, and then have to tack on the 5. If you store partitions for numbers of the form `p^k, p^a * q^b, p^a * q^b *r^c, ...` for ... – Daniel Fischer Dec 21 '11 at 23:56
  • 1
    ... all small enough exponents and up to four or five prime factors, you can reuse them. Of course, I didn't mean to write 'partitions', it should have been 'partition templates', so e.g. `(2,1)` stands for a number of the form `p^2*q`, the set of partitions is `(p^2*q); (p^2, q); (p*q, p); (p, p, q)`, which could be stored as the template `([2,1]); ([2], [1]); ([1,1], [1]); ([1], [1],[1])`, and whenever you have a number of that form,you only need to fill in `p` and `q`. And if you have a number `p^2*q*r`, fill `p` and `q` in that template and tack on `r`. But whether it's faster...? – Daniel Fischer Dec 22 '11 at 00:07
  • Just accepted it as answer, as it seems I forgot to accept it as answer before - better late than never :) – TCSGrad Mar 06 '19 at 18:38
0

Why dont you find all the numbers that can divide the number and then you find permutations of the numbers that multiplications will add up to the number?

Finding all numbers that can divide your number takes O(n).

Then you can permute this set to find all possible sets that multiplication of this set will give you the number.

Once you find set of all possible numbers that divide the original number, then you can do dynamic programming on them to find the set of numbers that multiplying them will give you the original number.

DarthVader
  • 52,984
  • 76
  • 209
  • 300
  • "Once you find set of all possible numbers that divide the original number, then you can do dynamic programming on them" - I was hoping for a more specific hint other than "doing dynamic programming" :). For instance, could you tell me how should I be using the partitions for a smaller integer while computing the partitions for a bigger integer ? – TCSGrad Dec 19 '11 at 09:57