Вариант IntegerPartition?

IntegerPartitions[n, {3, 10}, Prime ~Array~ 10]

В Mathematica это даст список всех способов получить n в виде суммы от трех до десяти первых десяти простых чисел, позволяя дублировать по мере необходимости.

Как я могу эффективно найти суммы, равные n, позволяя каждому элементу использоваться только один раз?

Использование первых десяти простых чисел является лишь игрушечным примером. Я ищу решение, которое справедливо для произвольных аргументов. В реальных случаях генерация всех возможных сумм, даже с использованием полиномиальных коэффициентов, занимает слишком много памяти.

Я забыл включить, что я использую Mathematica 7.

Ответ 1

Следующее построит двоичное дерево, а затем проанализирует его и извлечет результаты:

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] := 
    [email protected][num, [email protected][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.

Edit:

Интересно, что простая memoization ускоряет решение примерно вдвое в примере, который я использовал ранее:

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}

Ответ 2

Может использовать 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}}}

--- редактировать --- Чтобы сделать это в версии 7, вместо "Решить" следует использовать "Сократить". Я свяжу это с одной функцией.

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]]]]]

Вывод [128] = {1.80373, 4660}

Не так быстро, как древовидный код, но все же (я думаю) разумное поведение. По крайней мере, не очевидно, необоснованно.

--- конец редактирования ---

Даниэль Лихтблау Wolfram Research

Ответ 3

Я хотел бы предложить решение, подобное духу Леониду, но более короткое и менее интенсивное в памяти. Вместо того, чтобы строить дерево и обрабатывать его, код перемещает дерево и Sow решение при обнаружении:

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, {}, [email protected][Cases[list, x_ /; x <= num]]]], 
   2, 1]]

Этот код медленнее, чем

In[177]:= 
UniqueIntegerParitions[200, Prime~Array~PrimePi[200]] // 
  Length // Timing

Out[177]= {0.499, 4660}

но использует память в > ~ 6 раз меньше, что позволяет идти дальше.