如何找到任何整数的乘法分区?
Posted
技术标签:
【中文标题】如何找到任何整数的乘法分区?【英文标题】:How to find multiplicative partitions of any integer? 【发布时间】:2012-01-23 09:41:37 【问题描述】:我正在寻找一种有效的算法来计算任何给定整数的乘法分区。比如12这样的分区数是4个,分别是
12 = 12 x 1 = 4 x 3 = 2 x 2 x 3 = 2 x 6
我已经为此阅读了wikipedia article,但这并没有真正为我提供生成分区的算法(它只讨论了此类分区的数量,老实说,即使这不是很清楚给我!)。
我正在研究的问题需要我为非常大的数字(> 10 亿)计算乘法分区,因此我试图为它提出一种动态编程方法(以便找到所有可能的分区当较小的数字本身是较大数字的一个因素时,数字可以重复使用),但到目前为止,我不知道从哪里开始!
任何想法/提示将不胜感激 - 这不是作业问题,只是我正在尝试解决的问题,因为它似乎非常有趣!
【问题讨论】:
通过接近投票,理想情况下应该有某种解释,说明您为什么认为这需要关闭,以便 OP 可以纠正他/她的错误(如果有的话)。孤独的亲近选民可以说出来吗? 没有任何解释的关闭投票 - 一直很喜欢那些! 我投错了票。我很抱歉。 @Mods,有什么方法可以编辑错误的接近投票? shan23,确实不应该有问题;要结束这个问题,还需要几票。如果确实发生这种情况,我将尽快重新投票。 【参考方案1】:我要做的第一件事是对数字进行素数分解。
从那里,我可以对因子的每个子集进行排列,乘以该迭代中的剩余因子。
所以如果你取一个像 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));
【讨论】:
将乘法加起来与原始数字相加的数字的排列。 (排列?组合?我忘记了正确的词)它应该是排列。 @glowcoder:一些问题 - 对于具有很多素因数的足够大的数字,大部分工作将在识别和删除重复分区方面完成。我一直在寻找一种方法来超越这一代本身。另外,factors.permutate(2) 有什么作用?我在 STL 中没有找到任何与之对应的 API,因此想知道“2”参数的意义。 @shan23 - 您可以进行一些优化以减少破坏性,但它本身就是一项昂贵的操作。permutate(2)
是一个错字 - 它应该是 permutate(i)
并且不,我不相信它在任何 STL 库中。它是一个函数的伪代码,它将返回一个值列表,每个值的大小为i
,在所有可能的子列表中完成。【参考方案2】:
当然,首先要做的是找到数字的素因数分解,就像glowcoder说的那样。说
n = p^a * q^b * r^c * ...
然后
-
找到
m = n / p^a
的乘法分区
对于0 <= k <= a
,求p^k
的乘法分区,相当于求k
的加法分区
对于m
的每个乘法分区,找出所有不同的方法将a-k
因子p
在因子之间分配
结合 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
当然特别容易,因为只涉及两个素数(但无平方数仍然更容易),阶乘更早变慢。我认为通过仔细组织顺序和选择比列表更好的数据结构,可以获得相当多的收益(可能应该按指数对主要因素进行排序,但我不知道是否应该从最高指数开始或最低)。
【讨论】:
我不知道 Haskell,但我假设你已经运行了你的代码 - 我很想知道大数字(比如 ~10,000,000,000)需要多少时间?当我最终用 C++ 编写我的解决方案时,它会让我知道会发生什么...... @shan23 我添加了一些时间。作为一个疯狂的猜测,10 倍的改进看起来并非不可能。 这是一个非常好的答案(有时间安排)——让我在周末尝试 C++,看看它是否会变得更好。另外,一个相关的查询 - 在计算更大数字的分区时,如何利用分区的 $n$,其中一个因素是 $n$?我正在寻找一系列数字 n...m 的分区,所以如果我能找到一种方法,这对我特别有用! 由于分区的形状只取决于素数分解中的指数,如果n
不是除数,我们也可以重用n
的分区来生成m
的分区在m
中,我们所需要的只是一个合适的质因式分解结构。比如12 = 2^2 * 3^1
,那么我们可以复用12的partition来查找90 = 2^1 * 3^2 * 5^1
的partition。我们只需要在 12 的分区中交换 2 和 3 即可得到 18 的分区,然后必须在 5 上添加。如果您将分区存储为 p^k, p^a * q^b, p^a * q^b *r^c, ...
形式的数字,则为 ...
...所有足够小的指数和最多四五个素数,您可以重复使用它们。当然,我并不是要写“分区”,它应该是“分区模板”,例如(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
,然后添加r
。但它是否更快......?【参考方案3】:
你为什么不找到所有可以整除数字的数字,然后找到乘法加起来的数字的排列?
找出所有可以整除你的数的数需要 O(n)。
然后你可以排列这个集合来找到所有可能的集合,这个集合的乘法会给你这个数字。
一旦你找到所有可能的数除以原始数的集合,然后你可以对它们进行动态编程以找到将它们相乘得到原始数的数集。
【讨论】:
“一旦你找到了所有可能的数字除以原始数字的集合,那么你就可以对它们进行动态编程”——我希望得到比“进行动态编程”更具体的提示:) .例如,你能告诉我在计算更大整数的分区时应该如何使用分区?以上是关于如何找到任何整数的乘法分区?的主要内容,如果未能解决你的问题,请参考以下文章
在Linux中不使用乘法运算符的情况下,两个整数乘积的汇编代码