有多少种方法可以表示一组给定的数字

时间:2011-11-06 11:41:07

标签: algorithm combinatorics

我想知道我们可以用多少种方式将数字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个。

3 个答案:

答案 0 :(得分:3)

由于总和顺序很重要,所以:

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

这足以满足动态编程解决方案的需求。如果从0到n创建值S(i,set),则复杂度为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 - max{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。

答案 1 :(得分:3)

如果要查找从给定数字集中表示数字N的所有可能方法,则应遵循已提出的动态编程解决方案。

但是如果你只是想知道方法的数量,那么你正在处理restricted partition function problem

  

受限分区函数p(n,dm)≡p(n,{d1,d2,...,   dm})是n个正整数{d1,d2,...}的分区。   。 。 ,dm},每个都不大于n。

您还应该查看partition function without restrictions上没有限制的维基百科文章。

PS。如果也允许负数,那么可能(可数)无限的方式来表示你的总和。

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

PS2。这是一个数学问题,而不是编程问题

答案 2 :(得分:3)

计算组合数可以在O(log x)中完成,忽略在任意大小的整数上执行矩阵乘法所需的时间。

组合的数量可以表示为复发。让S(n)为通过从集合中添加数字来设置数字n的方式的数量。重现是

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

其中a_i是集合中i出现的次数。而且,对于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.

为了将运行时间降至O(log x),可以使用exponentiation by squaring来计算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;
}