Search code examples
calgorithmbit-manipulation

Optimal Selection for minimum total sum


This is a problem from competitive programmer's handbook:
We are given the prices of k products over n days, and we want to buy each product exactly once. However, we are allowed to buy at most one product in a day. What is the minimum total price?

Day 0 1 2 3 4 5 6 7
Product 0 6 9 5 2 8 9 1 6
Product 1 8 2 6 2 7 5 7 2
Product 2 5 3 9 7 3 5 1 4

The Optimal Selection is:

  • product 0 on day 3 at price 2,
  • product 1 on day 1 at price 2,
  • product 2 on days 6 at price 1.

which gives us the total of 5.

The solution:
We either do not buy any product on day d or buy a product x that belongs to set S. In the latter case, we remove x from set S and add the price of x to the total price.

Here's the code from book:

#include <stdio.h>
#ifndef min
    #define min(a, b) ((a) < (b) ? (a) : (b))
#endif

int main()
{
    int price[3][8] = {{ 6, 9, 5, 2, 8, 9, 1, 6 },
                       { 8, 2, 6, 2, 7, 5, 7, 2 },
                       { 5, 3, 9, 7, 3, 5, 1, 4 }};
    int n = 8, k = 3;
    int total[1<<10][10];
    //Buy all products on day 0
    for (int x = 0; x < k; x++) {
        total[1<<x][0] = price[x][0];
    }

    for (int d = 1; d < n; d++) {
        for (int s = 0; s < (1<<k); s++) {
            total[s][d] = total[s][d-1];
            for (int x = 0; x < k; x++) {
                if (s & (1<<x)) {
                    total[s][d] = min(total[s][d], total[s ^ (1<<x)][d-1] + price[x][d]);
                    break;
                }
            }
        }
    }
    //Output    
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            printf("%d", total[i][j]);
        }
        printf("\n");
    }
}

The problem restricts us to buy only one product a day but the code seems to not address that issue at all (also, we buy all products on first day which is fine). The output is just the minimum for each product available by that day [1,2,1]. What am I doing wrong here?


Solution

  • Turns out the solution that was provided in the book was incomplete. For the program to return the correct result, all subsets of first day have to be populated but in the book only the subsets containing single element that were mapped to powers of two i.e., the indices 1,2,4,etc of total[][] were populated which left the other subsets to have value of 0. This made each of the subsequent day calculation to take minimum value which is 0. code in line 14 to 16

    for (int x = 0; x < k; x++) {
            total[1<<x][0] = price[x][0];
        }
    

    must be replaced with:

      for (int s = 0; s < (1 << k); s++) {
        for (int x = 0; x < k; x++) {
          if (s & (1 << x)) {
            total[s][0] = price[x][0];
          }
        }
      }
    

    Minimum Total Sum for each day will be the set that contains all the elements i.e. total[(1<<k)-1][index of day]. With all the changes the working code is:

    #include <stdio.h>
    
    #ifndef min
     #define min(a, b)((a) < (b) ? (a) : (b))
    #endif
    
    int main()
    {
        int price[3][8] = {
            { 6, 9, 5, 2, 8, 9, 1, 6 },
            { 8, 2, 6, 2, 7, 5, 7, 2 },
            { 5, 3, 9, 7, 3, 5, 1, 4 }
        };
        int n = 8, k = 3;
    
        //Changed to scale with input
        int total[1 << k][n];
    
        //Buy all products on day 0
        //Changes here
        for (int s = 0; s < (1 << k); s++)
        {
            for (int x = 0; x < k; x++)
            {
                if (s &(1 << x))
                {
                    total[s][0] = price[x][0];
                }
            }
        }
    
        for (int d = 1; d < n; d++)
        {
            for (int s = 0; s < (1 << k); s++)
            {
                total[s][d] = total[s][d - 1];
                for (int x = 0; x < k; x++)
                {
                    if (s &(1 << x))
                    {
                        total[s][d] = min(total[s][d], total[s ^ (1 << x)][d - 1] + price[x][d]);
                        break;
                    }
                }
            }
        }
    
        //Output
        //Changes here    
        printf("%d", total[(1 << k) - 1][n - 1]);
    
    }