Как найти мультипликативные разделы любого целого?

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

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

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

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

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

Ответ 1

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

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

Тогда

  • найдите мультипликативные разделы m = n / p^a
  • для 0 <= k <= a, найдите мультипликативные разделы p^k, что эквивалентно нахождению аддитивных разделов k
  • для каждого мультипликативного разбиения m, найти все различные способы распределения факторов a-k p среди факторов
  • объединить результаты 2. и 3.

Удобно рассматривать мультипликативные разделы как списки (или множества) пар (divisor, multiple), чтобы избежать дублирования.

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

Ответ 2

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

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

Итак, если вы берете число, равное 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 a duplicate
        partition.add(new FactorSet(subsetTotal,otherSubsetTotal));
    }
}

Ответ 3

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

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

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

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