【问题标题】:How many ways to represent a number from a given set of numbers从给定的一组数字中表示一个数字的方法有多少
【发布时间】:2011-11-06 11:41:07
【问题描述】:

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

【问题讨论】:

  • 如果每个数字只能使用一次,这个问题绝对是 NP-Hard。我认为每个数字的无限使用也是如此,尽管目前我还没有想到减少
  • @Amit。是的,那么它就变成了 NP-Hard 的 Subset-Sum 问题。但这个不是。我猜它与数字的(受限)组合有关。
  • 由于您想将其识别为“不同”的方式,这很难。如果 F(4) 是 3 或 32,那就容易多了。
  • 这个问题与编程无关。它更适合数学 StackExchange。
  • 如果集合中的数字是以某个值为界的整数,那么您可以使用动态规划。

标签: algorithm combinatorics


【解决方案1】:

可以在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 在集合中出现的次数。此外,对于 nA 来表述(或更小是集合中的最大数更小)。那么,如果你有一个列向量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;
}

【讨论】:

  • 非常好 :-) 方法清晰且易于实施。
【解决方案2】:

如果您想从给定的一组数字中找到表示数字 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。这更像是一道数学题,而不是编程题

【讨论】:

  • 您能否给出一些具体的公式,并对其进行一些计算。我需要写一段代码。 PS:请注意,排序很重要,因此分区函数可能不够。是的,数字不能是负数
【解决方案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 &gt;= L, R &gt; 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。

【讨论】:

  • 我无法承受线性复杂度,因为 n 可以达到 10^18,我需要我的代码在几秒钟内运行。无论如何,谢谢。
  • 我很困惑,因为这应该是一个简单的问题。这是示例测试用例之一:- 输入:x=13 并且设置为 {1,2,8}。输出 = 415
  • 另一个示例测试用例:输入:x=15,设置为 {1,2,3,4,5}。输出 = 13624
  • @Ante。我不必担心输出快速增长,因为我必须以大素数为模打印输出,即。 1000000007.
  • 嗯,我需要一个可以给出 x=10^18 的输出并设置 {1,2,3,4,5,6,7,8,9,10,11, 12,13,14,15} 在一秒钟左右。而且我知道它肯定是可以解决的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多