Вариант IntegerPartition?
IntegerPartitions[n, {3, 10}, Prime ~Array~ 10]
В Mathematica это даст список всех способов получить n в виде суммы от трех до десяти из первых десяти простых чисел, позволяя дублировать при необходимости.
Как я могу эффективно найти суммы, равные n, позволяя использовать каждый элемент только один раз?
Использование первых десяти простых чисел - всего лишь игрушечный пример. Я ищу решение, которое подходит для произвольных аргументов. В реальных случаях генерация всех возможных сумм, даже с использованием полиномиальных коэффициентов, занимает слишком много памяти.
Я забыл указать, что я использую Mathematica 7.
3 ответа
Следующее создаст двоичное дерево, а затем проанализирует его и извлечет результаты:
Clear[intParts];
intParts[num_, elems_List] /; Total[elems] < num := p[];
intParts[num_, {fst_, rest___}] /;
fst < num := {p[fst, intParts[num - fst, {rest}]], intParts[num, {rest}]};
intParts[num_, {fst_, rest___}] /; fst > num := intParts[num, {rest}];
intParts[num_, {num_, rest___}] := {pf[num], intParts[num, {rest}]};
Clear[nextPosition];
nextPosition =
Compile[{{pos, _Integer, 1}},
Module[{ctr = 0, len = Length[pos]},
While[ctr < len && pos[[len - ctr]] == 1, ++ctr];
While[ctr < len && pos[[len - ctr]] == 2, ++ctr];
Append[Drop[pos, -ctr], 1]], CompilationTarget -> "C"];
Clear[getPartitionsFromTree, getPartitions];
getPartitionsFromTree[tree_] :=
Map[Extract[tree, #[[;; -3]] &@FixedPointList[nextPosition, #]] &,
Position[tree, _pf, Infinity]] /. pf[x_] :> x;
getPartitions[num_, elems_List] :=
getPartitionsFromTree@intParts[num, Reverse@Sort[elems]];
Например,
In[14]:= getPartitions[200,Prime~Array~150]//Short//Timing
Out[14]= {0.5,{{3,197},{7,193},{2,5,193},<<4655>>,{3,7,11,13,17,19,23,29,37,41},
{2,3,5,11,13,17,19,23,29,37,41}}}
Это не безумно быстро, и, возможно, алгоритм можно было бы оптимизировать дальше, но, по крайней мере, количество разделов не растет так быстро, как для IntegerPartitions
,
Редактировать:
Интересно, что простое запоминание ускоряет решение примерно вдвое на примере, который я использовал ранее:
Clear[intParts];
intParts[num_, elems_List] /; Total[elems] < num := p[];
intParts[num_, seq : {fst_, rest___}] /; fst < num :=
intParts[num, seq] = {p[fst, intParts[num - fst, {rest}]],
intParts[num, {rest}]};
intParts[num_, seq : {fst_, rest___}] /; fst > num :=
intParts[num, seq] = intParts[num, {rest}];
intParts[num_, seq : {num_, rest___}] :=
intParts[num, seq] = {pf[num], intParts[num, {rest}]};
Сейчас,
In[118]:= getPartitions[200, Prime~Array~150] // Length // Timing
Out[118]= {0.219, 4660}
Можно использовать Solve over Integer с множителями, ограниченными от 0 до 1. Я покажу для конкретного примера (первые 10 простых чисел, добавьте к 100), но для этого легко сделать общую процедуру.
primeset = Prime[Range[10]];
mults = Array[x, Length[primeset]];
constraints01 = Map[0 <= # <= 1 &, mults];
target = 100;
Timing[res = mults /.
Solve[Flatten[{mults.primeset == target, constraints01}],
mults, Integers];
Map[Pick[primeset, #, 1] &, res]
]
Out [178] = {0,004, {{7, 11, 13, 17, 23, 29}, {5, 11, 13, 19, 23, 29}, {5, 7, 17, 19, 23, 29}, {2, 5, 11, 13, 17, 23, 29}, {2, 3, 11, 13, 19, 23, 29}, {2, 3, 7, 17, 19, 23, 29}, { 2, 3, 5, 7, 11, 13, 17, 19, 23}}}
--- edit --- Чтобы сделать это в версии 7, нужно использовать Reduce вместо Solve. Я объединю это в одну функцию.
knapsack[target_, items_] := Module[
{newset, x, mults, res},
newset = Select[items, # <= target &];
mults = Array[x, Length[newset]];
res = mults /.
{ToRules[Reduce[
Flatten[{mults.newset == target, Map[0 <= # <= 1 &, mults]}],
mults, Integers]]};
Map[Pick[newset, #, 1] &, res]]
Вот пример Леонида Шифрина:
Timing[Length[knapsack[200, Prime[Range[150]]]]]
Out [128] = {1,80373, 4660}
Не так быстро, как древовидный код, но все же (я думаю) разумное поведение. По крайней мере, не очевидно, что это необоснованно.
--- конец редактирования ---
Даниэль Лихтблау Вольфрам Исследования
Я хотел бы предложить решение, похожее по духу на Леонида, но более короткое и менее требовательное к памяти. Вместо того, чтобы строить дерево и обрабатывать его, код обходит дерево и Sow
s решение, когда найдено:
Clear[UniqueIntegerParitions];
UniqueIntegerParitions[num_Integer?Positive,
list : {__Integer?Positive}] :=
Block[{f, $RecursionLimit = Infinity},
f[n_, cv_, {n_, r___}] :=
(Sow[Flatten[{cv, n}]]; f[n, cv, {r}];);
f[n_, cv_, {m_, r___}] /; m > n := f[n, cv, {r}];
f[n_, cv_, {m_, r___}] /;
Total[{r}] >= n - m := (f[n - m, {cv, m}, {r}]; f[n, cv, {r}]);
f[___] := Null;
Part[Reap[f[num, {}, Reverse@Union[Cases[list, x_ /; x <= num]]]],
2, 1]]
Этот код медленнее, чем у Леонида
In[177]:=
UniqueIntegerParitions[200, Prime~Array~PrimePi[200]] //
Length // Timing
Out[177]= {0.499, 4660}
но использует примерно в ~ 6 раз меньше памяти, что позволяет идти дальше.