任意の整数の乗法的なパーティションを見つけるには?

私は任意の整数のための乗法的なパーティションを計算するための効率的なアルゴリズムを探しています。例えば、12の場合のそのようなパーティションの数は4です。

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

これについては、ウィキペディアの記事を読んだことがありますが、これはパーティションを生成する(それはそのようなパーティションの数について話しているだけで、正直言ってもそれは私にはあまり明確ではない!)。

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!

どんなアイデアやヒントもありがたいです - これは宿題の問題ではなく、私が解決しようとしているものはとても興味深いそうです。

12
私は間違いで票を投じた。謝罪いたします。
追加された 著者 Mr.Wizard,
shan23、それは本当に問題を引き起こすべきではありません。この質問が閉鎖されるにはさらに数票が必要になります。それが起これば、私はできるだけ早く再票を投じます。
追加された 著者 Mr.Wizard,
近い投票では、OPが(もしあれば)自分の間違いを是正できるように、なぜこれを閉じなければならないと思うかについての何らかの説明が理想的です。孤独なクローズアップ投票者が話せますか?
追加された 著者 TCSGrad,
何の説明もせずにクローズ票を - 常にそれらを愛して!
追加された 著者 TCSGrad,
@Mods、誤ってクローズド・キャストを修正する方法はありますか?
追加された 著者 TCSGrad,
@ Mr.Wizard - ありがとう!
追加された 著者 TCSGrad,

3 答え

私が最初に行うことは、数の素因数分解を得ることです。

そこから、私はその要素の各部分集合の順列を作り、その反復における残りの要素を乗じることができます。

あなたが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 subset : factors.permutate(2)) {
        List 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));
    }
}
4
追加された
乗算が元の数に加算される数の順列。
追加された 著者 DarthVader,
(順列?combintion?私は正しい単語を忘れる)それは順列でなければなりません。
追加された 著者 DarthVader,
@ shan23 - ダメージを少なくするためにできる最適化がいくつかありますが、それは本質的に高価な操作です。 permutate(2)はtypoです。これは permutate(i)である必要があります。すべての可能なサブリストにわたって、 i のサイズのリストを返す関数の擬似コードです。
追加された 著者 corsiKa,
@glowcoder:いくつかの問題 - プライムファクタが多い十分な数のため、重複したパーティションの特定と削除に多くの作業が必要です。私は世代そのものの間にそれを過ごす方法を模索していました。また、factor.permutate(2)は何をしますか?私はそれに対応するSTLでAPIを見つけられなかったので、 "2"パラメータの意義について疑問を抱いていました。
追加された 著者 TCSGrad,

もちろん、最初にやるべきことは、数字の素因数分解を見つけることです、グローコーダーは言ったように。いう

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

その後、

  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.

乗法区画を(除数、多重度)対のリスト(または集合)として扱い、重複を避けることが便利である。

私は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 その後、 [[]] 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)

2つの素数しか含まれていないので(もちろん、方形の数字はまだ簡単です)、 10 ^ k は特に簡単です。リストよりも優れたデータ構造の慎重な編成と選択によって、かなりのものが得られるはずです(おそらく、指数で素因数をソートする必要がありますが、最も高い指数で始めるべきか、最も低い)。

4
追加された
@ shan23私はいくつかのタイミングを追加しました。野生の推測として、10の改善要因は不可能に見えません。
追加された 著者 Daniel Fischer,
パーティションの形状は素因数分解の指数にのみ依存するので、 n の場合は m のパーティションを生成するために、/code>は m の除数ではないので、素因数分解の適切な構造が必要です。たとえば、 12 = 2 ^ 2 * 3 ^ 1 のパーティションを再利用して、 90 = 2 ^ 1 * 3 ^ 2 * 5 ^ 1 p ^ k、p ^ a * qという形式の番号のパーティションを格納する場合は、12のパーティションで2と3を交換して18のパーティションを取得しなければなりません。 ^ b、p ^ a * q ^ b * r ^ c、... ... ...
追加された 著者 Daniel Fischer,
...すべての小さな指数と最大4つまたは5つの素因数は、それらを再利用することができます。もちろん、私は 'パーティション'を書くことを意味しませんでした、それは 'パーティションテンプレート'だったはずです。 (2,1) p ^ 2 * q という形式の番号を表し、パーティションの集合は(p ^ 2 * q)です。 (p ^ 2、q); (p * q、p); (p、p、q)はテンプレート([2,1])として保存できます。 ([2]、[1]); ([1,1]、[1]); ([1]、[1]、[1])であり、その形式の番号があるときは、 p q >。また、 p ^ 2 * q * r という数字がある場合は、そのテンプレートに p q /code>しかし、それがより速いかどうか...?
追加された 著者 Daniel Fischer,
私はハスケルを知らないが、あなたのコードを実行したと仮定している - 私は、何百万という大きな数のためにどのような時間がかかるのか知ることに興味があったのだろうか?それは私に最終的に私のソリューションをC ++でコーディングするときに何を期待するかというアイデアを与えるでしょう...
追加された 著者 TCSGrad,
Thats本当に素晴らしい答え(タイミングあり) - 週末にC ++で試してみましょう。また、関連するクエリ - より大きい数のパーティションを計算するとき、$ n $のパーティションをどのように利用するのですか?その要素の1つは$ n $ですか?私は数字の範囲のパーティションを取得するために探しています... m、これは私がそれのための方法を見つけ出すことができればこれは私にとって特に便利です!
追加された 著者 TCSGrad,

なぜあなたは数を分けることができるすべての数を見つけられないのですか?次に、乗算が数に加算される数の順列を見つけますか?

あなたの番号を分けることができるすべての数字を見つけることは、O(n)を取る。

次に、このセットを並べ替えることで、可能なすべてのセットを見つけることができます。このセットを乗算すると、番号が与えられます。

元の番号を分割するすべての可能な番号のセットを見つけたら、動的なプログラミングを行い、それらを乗算して元の番号を与える一連の番号を見つけることができます。

0
追加された
「元の番号を分けるすべての数値を見つけたら、動的プログラミングを行うことができます」 - 「動的プログラミングを行う」以外の具体的なヒントが期待されていました。たとえば、より大きな整数のパーティションを計算するときに、より小さい整数のパーティションを使用する方法を教えてください。
追加された 著者 TCSGrad,