有多少种方法来表示给定数字集中的一个数字

发布于 2024-12-13 16:47:36 字数 321 浏览 1 评论 0原文

我想知道我们可以用多少种方式将数字 x 表示为给定数字集合 {a1.a2,a3,...} 中的数字之和。每个数字可以多次被取走。

例如,如果x=4且a1=1,a2=2,则表示x=4的方式为:

1+1+1+1
1+1+2
1+2+1
2+1+1
2+2

因此方式的数量=5。

我想知道是否存在一个公式或其他一些快速方法可以做到这一点。我无法用暴力破解它。我想为它编写代码。

注意:x 可以大到 10^18。 a1,a2,a3,… 项的数量最多可以为 15,并且 a1,a2,a3,… 中的每一项也最多只能为 15。

I want to know in how many ways can we represent a number x as a sum of numbers from a given set of numbers {a1.a2,a3,...}. Each number can be taken more than once.

For example, if x=4 and a1=1,a2=2, then the ways of representing x=4 are:

1+1+1+1
1+1+2
1+2+1
2+1+1
2+2

Thus the number of ways =5.

I want to know if there exists a formula or some other fast method to do so. I can't brute force through it. I want to write code for it.

Note: x can be as large as 10^18. The number of terms a1,a2,a3,… can be up to 15, and each of a1,a2,a3,… can also be only up to 15.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

吾家有女初长成 2024-12-20 16:47:36

计算组合的数量可以在 O(log x) 内完成,无需考虑对任意大小的整数执行矩阵乘法所需的时间。

组合的数量可以表示为递归。令S(n) 为通过将一组数字相加得到n 的方法数。重复率是

S(n) = a_1*S(n-1) + a_2*S(n-2) + ... + a_15*S(n-15),

其中 a_ii 在集合中出现的次数。另外,对于n<0,S(n)=0。这种递归可以用大小为 15*15 的矩阵 A 来表示(或更小是集合中最大的数更小)。然后,如果您有一个列向量 V

S(n-14) S(n-13) ... S(n-1) S(n),

则矩阵乘法 A*V 的结果将是

S(n-13) S(n-12) ... S(n) S(n+1).

A 矩阵定义如下:

0    1    0    0    0    0    0    0    0    0    0    0    0    0    0
0    0    1    0    0    0    0    0    0    0    0    0    0    0    0
0    0    0    1    0    0    0    0    0    0    0    0    0    0    0
0    0    0    0    1    0    0    0    0    0    0    0    0    0    0
0    0    0    0    0    1    0    0    0    0    0    0    0    0    0
0    0    0    0    0    0    1    0    0    0    0    0    0    0    0
0    0    0    0    0    0    0    1    0    0    0    0    0    0    0
0    0    0    0    0    0    0    0    1    0    0    0    0    0    0
0    0    0    0    0    0    0    0    0    1    0    0    0    0    0
0    0    0    0    0    0    0    0    0    0    1    0    0    0    0
0    0    0    0    0    0    0    0    0    0    0    1    0    0    0
0    0    0    0    0    0    0    0    0    0    0    0    1    0    0
0    0    0    0    0    0    0    0    0    0    0    0    0    1    0
0    0    0    0    0    0    0    0    0    0    0    0    0    0    1
a_15 a_14 a_13 a_12 a_11 a_10 a_9  a_8  a_7  a_6  a_5  a_4  a_3  a_2  a_1 

其中 a_i 的定义如上。通过手动执行乘法,可以立即看到该矩阵与 S(n_14) ... S(n) 向量相乘有效的证明;向量中的最后一个元素将等于 n+1 递归式的右侧。通俗地说,矩阵中的元素将列向量中的元素向上移动一行,矩阵的最后一行计算最新项。

为了计算递推的任意项S(n),需要计算A^n * V,其中V等于

S(-14) S(-13) ... S(-1) S(0) = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.

In为了将运行时间降低到O(log x),可以使用求幂平方来计算A^n

事实上,完全忽略列向量就足够了,A^n 的右下元素包含所需的值S(n)

如果上面的解释很难理解,我提供了一个 C 程序,它可以按照我上面描述的方式计算组合的数量。请注意,它很快就会溢出 64 位整数。使用GMP,您将能够更进一步地获得高精度浮点类型,尽管您不会得到一个确切的答案。

不幸的是,我找不到一种快速方法来获得 x=10^18 等数字的准确答案,因为答案可能比 10^x 大得多。

#include <stdio.h>
typedef unsigned long long ull;

/*  highest number in set */
#define N 15

/*  perform the matrix multiplication out=a*b */
void matrixmul(ull out[N][N],ull a[N][N],ull b[N][N]) {
  ull temp[N][N];
  int i,j,k;
  for(i=0;i<N;i++) for(j=0;j<N;j++) temp[i][j]=0;
  for(k=0;k<N;k++) for(i=0;i<N;i++) for(j=0;j<N;j++)
    temp[i][j]+=a[i][k]*b[k][j];
  for(i=0;i<N;i++) for(j=0;j<N;j++) out[i][j]=temp[i][j];
}

/*  take the in matrix to the pow-th power, return to out */
void matrixpow(ull out[N][N],ull in[N][N],ull pow) {
  ull sq[N][N],temp[N][N];
  int i,j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) temp[i][j]=i==j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) sq[i][j]=in[i][j];
  while(pow>0) {
    if(pow&1) matrixmul(temp,temp,sq);
    matrixmul(sq,sq,sq);
    pow>>=1;
  }
  for(i=0;i<N;i++) for(j=0;j<N;j++) out[i][j]=temp[i][j];
}

void solve(ull n,int *a) {
  ull m[N][N];
  int i,j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) m[i][j]=0;
  /*  create matrix from a[] array above */
  for(i=2;i<=N;i++) m[i-2][i-1]=1;
  for(i=1;i<=N;i++) m[N-1][N-i]=a[i-1];
  matrixpow(m,m,n);
  printf("S(%llu): %llu\n",n,m[N-1][N-1]);
}

int main() {
  int a[]={1,1,0,0,0,0,0,1,0,0,0,0,0,0,0};
  int b[]={1,1,1,1,1,0,0,0,0,0,0,0,0,0,0};
  solve(13,a);
  solve(80,a);
  solve(15,b);
  solve(66,b);
  return 0;
}

Calculating the number of combinations can be done in O(log x), disregarding the time it takes to perform matrix multiplication on arbitrarily sized integers.

The number of combinations can be formulated as a recurrence. Let S(n) be the number of ways to make the number n by adding numbers from a set. The recurrence is

S(n) = a_1*S(n-1) + a_2*S(n-2) + ... + a_15*S(n-15),

where a_i is the number of times i occurs in the set. Also, S(n)=0 for n<0. This kind of recurrence can be formulated in terms of a matrix A of size 15*15 (or less is the largest number in the set is smaller). Then, if you have a column vector V containing

S(n-14) S(n-13) ... S(n-1) S(n),

then the result of the matrix multiplication A*V will be

S(n-13) S(n-12) ... S(n) S(n+1).

The A matrix is defined as follows:

0    1    0    0    0    0    0    0    0    0    0    0    0    0    0
0    0    1    0    0    0    0    0    0    0    0    0    0    0    0
0    0    0    1    0    0    0    0    0    0    0    0    0    0    0
0    0    0    0    1    0    0    0    0    0    0    0    0    0    0
0    0    0    0    0    1    0    0    0    0    0    0    0    0    0
0    0    0    0    0    0    1    0    0    0    0    0    0    0    0
0    0    0    0    0    0    0    1    0    0    0    0    0    0    0
0    0    0    0    0    0    0    0    1    0    0    0    0    0    0
0    0    0    0    0    0    0    0    0    1    0    0    0    0    0
0    0    0    0    0    0    0    0    0    0    1    0    0    0    0
0    0    0    0    0    0    0    0    0    0    0    1    0    0    0
0    0    0    0    0    0    0    0    0    0    0    0    1    0    0
0    0    0    0    0    0    0    0    0    0    0    0    0    1    0
0    0    0    0    0    0    0    0    0    0    0    0    0    0    1
a_15 a_14 a_13 a_12 a_11 a_10 a_9  a_8  a_7  a_6  a_5  a_4  a_3  a_2  a_1 

where a_i is as defined above. The proof that the multiplication of this matrix with a vector of S(n_14) ... S(n) works can be immediately seen by performing the multiplication manually; the last element in the vector will be equal to the right hand side of the recurrence with n+1. Informally, the ones in the matrix shifts the elements in the column vector one row up, and the last row of the matrix calculates the newest term.

In order to calculate an arbitrary term S(n) of the recurrence is to calculate A^n * V, where V is equal to

S(-14) S(-13) ... S(-1) S(0) = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.

In order to get the runtime down to O(log x), one can use exponentiation by squaring to calculate A^n.

In fact, it is sufficient to ignore the column vector altogether, the lower right element of A^n contains the desired value S(n).

In case the above explanation was hard to follow, I have provided a C program that calculates the number of combinations in the way I described above. Beware that it will overflow a 64-bits integer very quickly. You'll be able to get much further with a high-precision floating point type using GMP, though you won't get an exact answer.

Unfortunately, I can't see a fast way to get an exact answer for numbers such at x=10^18, since the answer can be much larger than 10^x.

#include <stdio.h>
typedef unsigned long long ull;

/*  highest number in set */
#define N 15

/*  perform the matrix multiplication out=a*b */
void matrixmul(ull out[N][N],ull a[N][N],ull b[N][N]) {
  ull temp[N][N];
  int i,j,k;
  for(i=0;i<N;i++) for(j=0;j<N;j++) temp[i][j]=0;
  for(k=0;k<N;k++) for(i=0;i<N;i++) for(j=0;j<N;j++)
    temp[i][j]+=a[i][k]*b[k][j];
  for(i=0;i<N;i++) for(j=0;j<N;j++) out[i][j]=temp[i][j];
}

/*  take the in matrix to the pow-th power, return to out */
void matrixpow(ull out[N][N],ull in[N][N],ull pow) {
  ull sq[N][N],temp[N][N];
  int i,j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) temp[i][j]=i==j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) sq[i][j]=in[i][j];
  while(pow>0) {
    if(pow&1) matrixmul(temp,temp,sq);
    matrixmul(sq,sq,sq);
    pow>>=1;
  }
  for(i=0;i<N;i++) for(j=0;j<N;j++) out[i][j]=temp[i][j];
}

void solve(ull n,int *a) {
  ull m[N][N];
  int i,j;
  for(i=0;i<N;i++) for(j=0;j<N;j++) m[i][j]=0;
  /*  create matrix from a[] array above */
  for(i=2;i<=N;i++) m[i-2][i-1]=1;
  for(i=1;i<=N;i++) m[N-1][N-i]=a[i-1];
  matrixpow(m,m,n);
  printf("S(%llu): %llu\n",n,m[N-1][N-1]);
}

int main() {
  int a[]={1,1,0,0,0,0,0,1,0,0,0,0,0,0,0};
  int b[]={1,1,1,1,1,0,0,0,0,0,0,0,0,0,0};
  solve(13,a);
  solve(80,a);
  solve(15,b);
  solve(66,b);
  return 0;
}
ˇ宁静的妩媚 2024-12-20 16:47:36

由于总和的顺序很重要,因此它成立:

S( n, {a_1, ..., a_k} ) = sum[ S( n - a_i, {a_1, ..., a_k} ) for i in 1, ..., k ].

这对于动态规划解决方案来说已经足够了。如果值 S(i, set) 从 0 到 n 创建,则复杂度为 O( n*k ) 。

编辑:只是一个想法。将一个求和视为一个序列 (s_1, s_2, ..., s_m)。序列第一部分的总和在某个时刻将大于n/2,令其对于索引j

s_1 + s_2 + ... + s_{j-1} < n / 2,
s_1 + s_2 + ... + s_j = S >= n / 2.

最多有k不同的和S,并且对于每个S,最多有k个可能的最后元素s_j。所有可能性 (S,s_j) 将序列总和分为 3 部分。

s_1 + s_2 + ... + s_{j-1} = L,
s_j,
s_{j+1} + ... + s_m = R.

它保持n/2 >= L, R > n/2 - 最大值{a_i}。这样,上面的公式就有了更复杂的形式:

S( n, set ) = sum[ S( n-L-s_j, set )*S( R, set ) for all combinations of (S,s_j) ].

我不确定,但我认为每一步都需要“创建”范围
S(x,set) 值,其中范围将按因子 max{a_i} 线性增长。

编辑 2:@Andrew 示例。第一种方法很容易实现,并且适用于“小”x。这是 python 代码:

def S( x, ai_s ):
  s = [0] * (x+1)
  s[0] = 1
  for i in xrange(1,x+1):
    s[i] = sum( s[i-ai] if i-ai >= 0 else 0 for ai in ai_s )
  return s[x]

S( 13, [1,2,8] )
S( 15, [1,2,3,4,5] )

此实现存在大 x 内存问题(在 python 中>10^5)。由于只需要最后的 max(a_i) 值,因此可以使用循环缓冲区来实现它。

这些值增长得非常快,例如 S(100000, [1,2,8] ) 约为 10^21503。

Since order in sum is important it holds:

S( n, {a_1, ..., a_k} ) = sum[ S( n - a_i, {a_1, ..., a_k} ) for i in 1, ..., k ].

That is enough for dynamic programming solution. If values S(i, set) are created from 0 to n, than complexity is O( n*k ).

Edit: Just an idea. Look at one summation as a sequence (s_1, s_2, ..., s_m). Sum of first part of sequence will be larger than n/2 at one point, let it be for index j:

s_1 + s_2 + ... + s_{j-1} < n / 2,
s_1 + s_2 + ... + s_j = S >= n / 2.

There are at most k different sums S, and for each S there are at most k possible last elements s_j. All of possibilities (S,s_j) split sequence sum in 3 parts.

s_1 + s_2 + ... + s_{j-1} = L,
s_j,
s_{j+1} + ... + s_m = R.

It hold n/2 >= L, R > n/2 - max{a_i}. With that, upper formula have more complicated form:

S( n, set ) = sum[ S( n-L-s_j, set )*S( R, set ) for all combinations of (S,s_j) ].

I'm not sure, but I think that with each step it will be needed to 'create' range of
S(x,set) values where range will grow linearly by factor max{a_i}.

Edit 2: @Andrew samples. It is easy to implement first method and it works for 'small' x. Here is python code:

def S( x, ai_s ):
  s = [0] * (x+1)
  s[0] = 1
  for i in xrange(1,x+1):
    s[i] = sum( s[i-ai] if i-ai >= 0 else 0 for ai in ai_s )
  return s[x]

S( 13, [1,2,8] )
S( 15, [1,2,3,4,5] )

This implementation has problem with memory for large x (>10^5 in python). Since only last max(a_i) values are needed it is possible to implement it with circular buffer.

These values grow very fast, e.g. S(100000, [1,2,8] ) is ~ 10^21503.

挽手叙旧 2024-12-20 16:47:36

如果您想从给定的一组数字中找到表示数字 N 的所有可能方法,那么您应该遵循已经提出的动态规划解决方案。

但如果您只想知道方法的数量,那么您正在处理受限分区函数问题

受限配分函数 p(n, dm) ≡ p(n, {d1, d2, . . . ,
dm}) 是将 n 划分为正整数 {d1, d2,... 的次数。
。 。 , dm},每个不大于n。

您还应该查看关于没有限制的分区函数的维基百科文章,其中没有限制申请。

附言。如果也允许负数,那么可能有(可数)无限种方式来表示您的总和。

1+1+1+1-1+1
1+1+1+1-1+1-1+1
etc...

PS2。这更像是一道数学问题,而不是编程问题

If you want to find all possible ways of representing a number N from a given set of numbers then you should follow a dynamic programming solution as already proposed.

But if you just want to know the number of ways, then you are dealing with the restricted partition function problem.

The restricted partition function p(n, dm) ≡ p(n, {d1, d2, . . . ,
dm}) is a number of partitions of n into positive integers {d1, d2, .
. . , dm}, each not greater than n.

You should also check the wikipedia article on partition function without restrictions where no restrictions apply.

PS. If negative numbers are also allowed then there probably are (countably )infinite ways to represent your sum.

1+1+1+1-1+1
1+1+1+1-1+1-1+1
etc...

PS2. This is more a math question than a programming one

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文