Efficient algorithm to calculate the sum of all k-products

一世执手 提交于 2019-12-03 03:20:57

Let F(X,k,n) be the k-product sum of first n elements of array X.

F(X,k,n) = F(X,k,n-1)+F(X,k-1,n-1)*X[n]

which you can solve using dynamic programming. Complexity = O(kn).

End conditions for F(X,k,n): When n=k F(X,k,k) = X[1]* X[2]*...*X[n]

More details:

F(X,1,1) = X[1]
F(X,1,i) = F(X,1,i-1)+X[i] for i=2...n 

For j=2..n:
    For i = 1..k:
        if i<j:
            F(X,i,j) = F(X,i,j-1)+F(X,i-1,j-1)*X[j]
        else if i==j:
            F(X,i,j) = F(X,i-1,j-1)*X[j]
        else:
            pass

Yes, there is a way. Consider the polynomial

(X + a[0]) * (X + a[1]) * ... * (X + a[n-1])

Its coefficients are just the sums of the k-products, its degree is n, so you can calculate the sum of all k-products for all k simultaneously in O(n^2) steps.

After s steps, the coefficient of Xs-k is the sum of the k-products of the first s array elements. The k-products of the first s+1 elements fall into two classes, those involving the (s+1)st element - these have the form a[s]*((k-1)-product of the first s elements) - and those not involving it - these are the k-products of the first s elements.

Code such that result[i] is the coefficient of Xi (the sum of the (n-i)-products):

int *k_products_1(int *a, int n){
    int *new, *old = calloc((n+1)*sizeof(int));
    int d, i;
    old[0] = 1;
    for(d = 1; d <= n; ++d){
        new = calloc((n+1)*sizeof(int));
        new[0] = a[d-1]*old[0];
        for(i = 1; i <= d; ++i){
            new[i] = old[i-1] + a[d-1]*old[i];
        }
        free(old);
        old = new;
    }
    return old;
}

If you only want the sum of the k-products for one k, you can stop the calculation at index n-k, giving an O(n*(n-k)) algorithm - that's good if k >= n/2. To get an O(n*k) algorithm for k <= n/2, you have to organise the coefficient array the other way round, so that result[k] is the coefficient of Xn-k (and stop the calculation at index k if you want only one sum):

int *k_products_2(int *a, int n){
    int *new, *old = calloc((n+1)*sizeof(int));
    int d, i;
    old[0] = 1;
    for(d = 1; d <= n; ++d){
        new = calloc((n+1)*sizeof(int));
        new[0] = 1;
        for(i = 1; i <= d; ++i){
            new[i] = old[i] + a[d-1]*old[i-1];
        }
        free(old);
        old = new;
    }
    return old;
}

You can reduce k by 1:

e.g. for k=2

1*3 + 1*4 + 1*6 + 3*4 + 3*6 + 4*6

==

1*(3+4+6)+3*(4+6)+4*6

and for k=3

1*3*4 + 1*3*6 + 3*4*6

==

1*3*(4+6) + 3*4*6

So basically you cycle your list, then recurse to the same algorithm with k reduced by 1 and only the rest of the list

For k=2,

let's s = SUM_x_in_L x (sum of the numbers) and sq = SUM_x_in_L x^2 (sum of the squares)

then it's SUM_x_in_L (s - x) * x / 2 = (s * s - sq) / 2

Algebraically, for k=2 just take the sum of the elements of L, square it, and subtract the sum of the squares of L. That is:

int sum = 0;
int sqSum = 0;
for (int i=0; i<n; ++i) {
    sum += L[i];
    sqSum += L[i]*L[i];
}
return sum*sum - sqSum;

In your example, what you are computing is this

(1 + 3 + 4 + 6)^2 - (1^2 + 3^2 + 4^2 + 6^2) = 1*3 + 1*4 + 1*6 + 3*4 + 3*6 + 4*6

This should give you a hint for how to proceed in general.

An interesting property you could explore is the distributive property of multiplication in relation to addition.

L=[a,b,c,d]

When k = 1, it's trivial:

S=a+b+c+d

When k = 2:

S = a * (b + c + d) + b * (c + d) + c * d

When k = 3, things get a bit more interesting:

S = a * b * ( c + d) + (c * d) * (a + b)
S = a * (b * (c + d)) + c * d) + b * (c * d)  <-- this form lends itself better to the algorithm

And for k = 4:

S = a * b * c * d

This should hold for larger values of n.

An implementation in C#:

 private static int ComputeSum(int[] array, int offset, int K)
 {
     int S = 0;

     if (K == 1)
     {                      
         for (int i = offset; i < array.Length; i++)
             S += array[i];
     }
     else if ((array.Length - offset) == K)
     {
         S = array[offset] * ComputeSum(array, offset + 1, K - 1);
     }
     else if (offset < array.Length)
     {
         S = ComputeSum(array, offset + 1, K) + array[offset] * ComputeSum(array, offset + 1, K - 1);             
     }

     return S;
 }

Which can be further improved by memoization.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!