Как найти мультипликативные разбиения любого целого числа? - PullRequest
13 голосов
/ 19 декабря 2011

Я ищу эффективный алгоритм для вычисления мультипликативных разделов для любого заданного целого числа.Например, количество таких разделов для 12 равно 4, что составляет

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

Я прочитал статья в Википедии для этого, но на самом деле это не дает мне алгоритм генерации разделов (он говорит только о количестве таких разделов, и, честно говоря, даже это не очень понятно для меня!).

Проблема, на которую я смотрю, требует от меня вычисления мультипликативных разделов для очень больших чисел (> 1 миллиарда), поэтому я пытался придумать подход динамического программирования для этого (чтобы найтивсе возможные разделы для меньшего числа могут быть повторно использованы, когда это меньшее число само по себе является фактором большего числа), но пока я не знаю, с чего начать!

Любые идеи / подсказкибыть оцененным - это не домашняя проблема, я просто пытаюсь ее решить, потому что кажется таким интересным!

Ответы [ 3 ]

6 голосов
/ 19 декабря 2011

Первое, что я хотел бы сделать, это получить простую факторизацию числа.

Оттуда я могу сделать перестановку каждого подмножества факторов, умноженных на оставшиеся факторы на этой итерации.

Итак, если вы возьмете число, например, 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));
    }
}
5 голосов
/ 21 декабря 2011

Конечно, первое, что нужно сделать, - это найти первичную факторизацию числа, как сказал Световой кодер. Say

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, конечно, особенно легко, потому что задействованы только два простых числа (но числа без квадратов все же проще), факториалы замедляются раньше. Я думаю, что при тщательной организации порядка и выбора более качественных структур данных, чем списков, можно получить немало (вероятно, следует отсортировать основные факторы по показателю степени, но я не знаю, следует ли начинать с самых высоких показателей или самый низкий).

0 голосов
/ 19 декабря 2011

Почему бы вам не найти все числа, которые могут разделить число, а затем вы найдете перестановки чисел, которые умножения будут складывать в число?

Нахождение всех чисел, которые могут разделить ваше число, занимает O (n).

Затем вы можете переставить этот набор, чтобы найти все возможные наборы, при которых умножение этого набора даст вам число.

Как только вы найдете набор всех возможных чисел, которые делят исходное число, вы можете выполнить динамическое программирование для них, чтобы найти набор чисел, умножая их, и даст вам исходное число.

...