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