Общее число разбиений
целого числа
на слагаемые вычисляют (для больших
) по формуле Рамануджана-Харди (быстро сходящийся ряд).
Однако, если вводить ограничения на разбиения, например "слагемые не превышают k" или "число слагаемых не больше чем k", то формулы в виде быстро сходящегося ряда нет.
Например, в pari/gp есть встроенная функция, которая легко (на моем компе за примерно 12 секунд) вычисляет
Однако, попытки вычислять количество разбиений
на не более чем
слагаемых уже при
требуют намного бОльшего времени, а также огромных ресурсов по памяти. Для некоторых
специального вида быстрые формулы существуют, но не в общем случае.
Изучение Википедии и смежных источников свелось к двум алгоритмам.
1. "Прямой" рекурсивный алгоритм.
Для исключения повторных вычислений одного и того же, этот алгоритм требует создания и наполнения массива размером примерно
элементов, так что при больших
просто не хватает памяти. Ну и медленно работает.
Код:
p(n,k) =
{
if(n==0 && k==0,return(0));
local(NP=vector(k,i,numbpart(i)),M=vector(n,i,vector(k)));
my(memo_p = (x,y)-> my(res);
if(y<2,return(y));
if(res=M[x][y],return(res));
res=if(y>=x,NP[x],self()(x,y-1)+self()(x-y,y));
M[x][y]=res;
return(res));
memo_p(n,k);
}
2. "Хитрый" крученый алгоритм, сути которого я не понимаю, но который использует для вычисления массив размером около
элементов, работает существенно быстрее. На pari/gp это выглядит так:
Код:
p_1(n,k) =
{
my(k1 = k+1, kx = k1, a = 1, t = vector(k1, i, vector(k)));
t[k1][1] = 1;
for (i = 1, n-1,
my(ky = a);
t[a] = vector(k, j, if (!(ky--), ky = k1);
if (j == 1, t[ky][1], t[kx][j-1] + t[ky][j]));
kx = a; if (a++ > k1, a = 1));
vecsum(t[kx]);
}
Внутренний цикл работает примерно
раз.
"Хитрый" алгоритм быстрее "Прямого" примерно в
раз:
? p(2000,1000)
*** vector: Warning: increasing stack size to 16000000.
*** vector: Warning: increasing stack size to 32000000.
time = 1,857 ms.
%1 = 4720819175618825183434073956853110605486740202
? > p_1(2000,1000)
*** vector: Warning: increasing stack size to 16000000.
time = 1,170 ms.
%2 = 4720819175618825183434073956853110605486740202Для сравнения, вычисление всех
и их суммы от
до
? sum(i=1,10000,numbpart(i));
time = 141 ms.А вопрос собсно -- исключая особые случаи (когда
имеет какой-то специальный вид), как наиболее эффективно (по потреблению памяти и быстроте работы) посчитать количество разбиений натурального числа
на не более чем
натуральных слагаемых? Какие еще есть алгоритмы?