3

In other words, given a set of n positive integers A and a threshold B, I want to find the smallest C so that:

  • C > B
  • C = A[1] * k[1] + A[2] * k[2] + ... + A[n] * k[n], k[i] are integers >= 0

As an example if A = { 6, 11, 16 } then the values we can obtain are: { 0, 6, 11, 12, 16, 17, 18, 22, 23, 24, 27, 28, 29, 30, 32 ... } so if B = 14 then C would be 16, B = 22 => C = 23, B = 18 => C = 22

This problem was given these constraints: 2 < n < 5000 0 < A[i] < 50000 and 1 < B < 10^9 ( this is why I got stuck ). Also you had to calculate for an array B of size < 1000 an array C (but this may not matter). And the algorithm should run in under 0.3 sec in C++.

An algorithm like the one described here solves it but it is not fast enough: https://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/
I calculate table until B + Amin because Amin * k <= B <= Amin * ( k + 1 ) <= B + Amin
Here's the algorithm (in pseudo C++):

int n, A[n], B;
int Amin; // smallest number from A

// table[i] will tell us if it is possible or not to obtain the number i
bool table[B + Amin];
table[0] = true;
for( int i = 0; i < n; ++i )
{
    int x = A[i]; // current number / denomination
    for( int j = x; j <= B + Amin; ++j )
        if( table[j - x] ) 
            table[j] = true;
}

// now we can do something like this:
int result = B + 1;
while( !table[result] ) 
    ++result;
 

This algorithm has a complexity of O(n*B) and I'm looking for something that is independent of B ( or maybe has O(log(B)) or O(sqrt(B)) )

Note: if we make the first requirement C >= B then the problem doesn't change ( just add +1 to B ) and we can ask it like this: If we have specific coins or banknotes ( infinite of them ) and want to purchase something with them, then what is the amount we can pay so that the cashier has to give back minimal change.


Things that I suspect may help:
https://en.wikipedia.org/wiki/Coin_problem
If Greatest Common Divisor ( x, y ) = 1 then anything higher than xy − x − y can be obtained using x and y.

https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm

https://en.wikipedia.org/wiki/Subset_sum_problem


Edit: added example and note.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
T1m3
  • 31
  • 5
  • You could count the number of consecutive amounts which can be built and if it is one less than the smallest A you can immediately return B + 1. – maraca Jan 09 '18 at 16:57
  • The problem seems indeed equivalent to the coin problem that you link (for the set of coprime numbers in the coin denominations). If the Frobenius number is <= B, then the answer is B + 1, otherwise it is the Frobenius number itself. – jdehesa Jan 09 '18 at 17:13
  • You can also normalize by dividing all A by the gcd of all A and B also (round down), multiply the solution by the gcd of all A to get the real value. Like this you can ensure that there will be a point where all amounts can be built (no gaps) and you could also check at each amount if (B-amount+1) % current A == 0 for early return. – maraca Jan 09 '18 at 17:13
  • Btw, a _possible_ (not sure how much overhead it would involve) optimization in your algorithm is the following one: if at any point you find that `table` has `Amin` contiguous `true` elements then you can build any number after those (as per the reasoning in the [McNugget numbers](https://en.wikipedia.org/wiki/Coin_problem#McNugget_numbers) section in the Wikipedia). – jdehesa Jan 09 '18 at 17:22
  • "the algorithm should run in under 0.3 sec in C++." This doesn't make a lot of sense. (Continues fiddling with the arduino) – n. m. could be an AI Jan 09 '18 at 17:35
  • 1
    https://en.wikipedia.org/wiki/Change-making_problem#Dynamic_programming_with_the_probabilistic_convolution_tree also outlines a W log(W) solution. – גלעד ברקן Jan 09 '18 at 21:35

1 Answers1

1

I don't think you can get better than O(n*B), because the Frobenius number (above this number all amounts can be built with the given denominations) of 49999 and 49998 is 2499750005 a lot bigger than 10^9 and you need to calculate the best value at least for some inputs. If gcd(A) > 1 then the Frobenius number doesn't exist, but this could be prevented by dividing all A and B (round down) by gcd(A) and multiply the C you get by gcd(A) to get the final result.

There is still a lot of room for improvements in your pseudo code. You look at all denominations almost B+Amin times and also set the value in the table to true multiple times.

The standard implementation would look something like this:

sort(A);
table[0] = true;
for (int i = A[0]; i <= B + A[0]; i++)
    for (int j = 0; j < n && A[j] <= i; j++)
        if (table[i - A[j]]) {
            table[i] = true;
            break;
        }

This is already a little better (note the break). I call it the backwards implementation, because you look back from all positions in the table to see if you can find a value which has the difference of one of the given denominations. You could also introduce a counter for the number of consecutive values set to true in the table (increase the counter when you set a value in the table to true, reset if the value couldn't be built, return B+1 if counter == A[0] - 1).

Maybe you could even get better results with a forward implementation, because the table can be very sparse, here table values which are false are skipped instead of denominations:

table[0] = true;
for (int i = 0; i <= B + Amin; i++)
    if (table[i])
        for (j = 0; j < n; j++)
            if (i + A[j] <= B + Amin)
                table[i + A[j]] = true;
maraca
  • 8,468
  • 3
  • 23
  • 45