Problem 4 - Backpacking

Here, we will discuss the solution to Backpacking from AIO 2024.

Solution

Subtask 1: Ci ≤ Ci+1 for all i

In this subtask, it is always cheaper to buy food in earlier towns. Ideally, we would buy all the food in town 1. However, this may not be possible, depending on the size of our backpack. Instead, at each town, we either:

Code for Subtask 1

# Open the input and output files.
input_file = open("backin.txt", "r")
output_file = open("backout.txt", "w")

# Read the values of N, K, D, and C from the input file.
input_line = input_file.readline().strip()
N, K = map(int, input_line.split())
input_line = input_file.readline().strip()
D = list(map(int, input_line.split()))
input_line = input_file.readline().strip()
C = list(map(int, input_line.split()))

answer = 0
need_to_buy = sum(D)
curr_backpack = 0

for i in range(N-1):
   # K-curr_backpack is the maximum number of cans we can fit in our backpack
   # need_to_buy is the number of extra cans we need to reach the final town
   to_buy = min(K-curr_backpack, need_to_buy)
   curr_backpack += to_buy
   need_to_buy -= to_buy
   answer += to_buy * C[i]

   # We walk from town i to town i+1, eating D[i] cans
   curr_backpack -= D[i]

# Write the answer to the output file.
output_file.write("%d\n" % (answer))

# Finally, close the input/output files.
input_file.close()
output_file.close()
#include <cstdio>
#include <algorithm>
int N, K, D[200005], C[200005];
int main(void) {
   /* Open the input and output files. */
   FILE *input_file = fopen("backin.txt", "r");
   FILE *output_file = fopen("backout.txt", "w");

   /* Read the values of N, K, D, and C from the input file. */
   fscanf(input_file, "%d%d", &N, &K);
   for (int i = 0; i < N-1; i++) {
       fscanf(input_file, "%d", &D[i]);
   }
   for (int i = 0; i < N; i++) {
       fscanf(input_file, "%d", &C[i]);
   }

   int answer = 0;
   int need_to_buy = 0;
   for (int i = 0; i < N-1; i++) {
       need_to_buy += D[i];
   }

   int curr_backpack = 0;
   for (int i = 0; i < N-1; i++) {
       // K-curr_backpack is the maximum number of cans we can fit in our backpack
       // need_to_buy is the number of extra cans we need to reach the final town
       int to_buy = std::min(K-curr_backpack, need_to_buy);
       curr_backpack += to_buy;
       need_to_buy -= to_buy;
       answer += to_buy * C[i];

       // We walk from town i to town i+1, eating D[i] cans
       curr_backpack -= D[i];
   }

   /* Write the answer to the output file. */
   fprintf(output_file, "%d\n", answer);

   /* Finally, close the input/output files. */
   fclose(input_file);
   fclose(output_file);
}

Subtask 2: K = 1000000

In this subtask, our backpack is big enough to fit all the cans we will ever need:

Consider an example with 7 towns that have costs 4, 6, 3, 5, 1, 2, 3:

Unfortunately, an algorithm with a time complexity of O(N2) is too slow for this subtask. Luckily, with some careful planning, we can do this process in O(N) time.

Code for Subtask 2

# Open the input and output files.
input_file = open("backin.txt", "r")
output_file = open("backout.txt", "w")

# Read the values of N, K, D, and C from the input file.
input_line = input_file.readline().strip()
N, K = map(int, input_line.split())
input_line = input_file.readline().strip()
D = list(map(int, input_line.split()))
input_line = input_file.readline().strip()
C = list(map(int, input_line.split()))

distance_to_end = sum(D)

# We start by buying every can at town 1
price_paid = C[0]
answer = price_paid*distance_to_end

for i in range(N-1):
   if C[i] < price_paid:
       # C[i] is the cheapest town so far
       # If we bought the remaining cans from this town,
       # we would save price_paid-C[i] dollars per can
       answer -= (price_paid-C[i]) * distance_to_end
       price_paid = C[i]

   distance_to_end -= D[i]

# Write the answer to the output file.
output_file.write("%d\n" % (answer))

# Finally, close the input/output files.
input_file.close()
#include <cstdio>
#include <algorithm>
int N, K, D[200005], C[200005];
int main(void) {
   /* Open the input and output files. */
   FILE *input_file = fopen("backin.txt", "r");
   FILE *output_file = fopen("backout.txt", "w");

   /* Read the values of N, K, D, and C from the input file. */
   fscanf(input_file, "%d%d", &N, &K);
   for (int i = 0; i < N-1; i++) {
       fscanf(input_file, "%d", &D[i]);
   }
   for (int i = 0; i < N; i++) {
       fscanf(input_file, "%d", &C[i]);
   }

   int distance_to_end = 0;
   for (int i = 0; i < N-1; i++) {
       distance_to_end += D[i];
   }

   int price_paid = C[0];
   int answer = price_paid*distance_to_end;
   for (int i = 0; i < N-1; i++) {
       if (C[i] < price_paid) {
           // C[i] is the cheapest town so far
           // If we bought the remaining cans from this town,
           // we would save price_paid-C[i] dollars per can
           answer -= (price_paid-C[i]) * distance_to_end;
           price_paid = C[i];
       }

       distance_to_end -= D[i];
   }

   /* Write the answer to the output file. */
   fprintf(output_file, "%d\n", answer);

   /* Finally, close the input/output files. */
   fclose(input_file);
   fclose(output_file);
}

Subtask 3: N ≤ 1000

This problem has many different solutions and we will present just one of them. Our solution combines some of the ideas from subtasks 1 and 2. We consider the towns one at a time, in order. At each town, we either:

See below for an example of this solution.

Similarly to Shopping Spree, we can use an exchange argument to prove that this greedy algorithm is correct:

What is the time complexity of this solution?

Code for Subtask 3

# distance_to_town[i] will store the distance from town 0 to town i (that is, D[0] + D[1] + ... + D[i-1])
distance_to_town = []
    
# firstCheaperAfter[i] will contain the index of the first town after i that is cheaper than i (or N-1) if no town is cheaper than i
firstCheaperAfter = []
    
# Open the input and output files.
input_file = open("backin.txt", "r")
output_file = open("backout.txt", "w")
    
# Read the values of N, K, D, and C from the input file.
input_line = input_file.readline().strip()
N, K = map(int, input_line.split())
input_line = input_file.readline().strip()
D = list(map(int, input_line.split()))
input_line = input_file.readline().strip()
C = list(map(int, input_line.split()))
    
answer = 0
    
# Compute the distance_to_town list
distance_to_town.append(0)
for i in range(1, N):
    distance_to_town.append(distance_to_town[i-1] + D[i-1])
    
# Compute the firstCheaperAfter array
for i in range(N):
    firstCheaperAfter.append(N-1)
    for j in range(i+1, N):
        if C[j] < C[i]:
            firstCheaperAfter[i] = j
            break
    
in_backpack = 0 # in_backpack stores the number of cans in our backpack at the current moment
for i in range(N):
    distance_to_cheaper_town = distance_to_town[firstCheaperAfter[i]] - distance_to_town[i]
    
    # our backpack can hold at most K cans
    distance_to_cheaper_town = min(distance_to_cheaper_town, K)
    
    # we need distance_to_cheaper_town cans to reach the next cheaper town, however we already have in_backpack
    # therefore distance_to_cheaper_town-in_backpack is the number of extra cans we want to buy
    # this value can be negative, which is why we max it with 0
    will_buy = max(0, distance_to_cheaper_town - in_backpack)
        
    answer += will_buy * C[i]
    in_backpack += will_buy
    
    # we now walk to the next town, eating D[i] cans
    if i < N-1:
        in_backpack -= D[i]
    
# Write the answer to the output file.
output_file.write("%d\n" % (answer))
    
# Finally, close the input/output files.
input_file.close()
output_file.close()
#include <cstdio>
#include <algorithm> // for std::min and std::max

int N, K, D[200005], C[200005];

/*
 * distance_to_town[i] will store the distance from town 0 to town i (that is, D[0] + D[1] + ... + D[i-1])
 */
int distance_to_town[200005];

/*
 * firstCheaperAfter[i] will contain the index of the first town after i that is cheaper than i (or N-1) if no town is cheaper than i
 */
int firstCheaperAfter[200005];

int answer;
int main(void) {
    /* Open the input and output files. */
    FILE *input_file = fopen("backin.txt", "r");
    FILE *output_file = fopen("backout.txt", "w");

    /* Read the values of N, K, D, and C from the input file. */
    fscanf(input_file, "%d%d", &N, &K);
    for (int i = 0; i < N-1; i++) {
        fscanf(input_file, "%d", &D[i]);
    }
    for (int i = 0; i < N; i++) {
        fscanf(input_file, "%d", &C[i]);
    }

    // Compute the distance_to_town array
    for (int i = 1; i < N; i++) {
        distance_to_town[i] = distance_to_town[i-1] + D[i-1];
    }

    // Compute the firstCheaperAfter array
    for (int i = 0; i < N; i++) {
        firstCheaperAfter[i] = N-1;
        for (int j = i+1; j < N; j++) {
            if (C[j] < C[i]) {
                firstCheaperAfter[i] = j;
                break;
            }
        }
    }

    int in_backpack = 0; // in_backpack stores the number of cans in our backpack at the current moment
    for (int i = 0; i < N; i++) {
        int distance_to_cheaper_town = distance_to_town[firstCheaperAfter[i]] - distance_to_town[i];
        
        // our backpack can hold at most K cans
        distance_to_cheaper_town = std::min(distance_to_cheaper_town, K);
        
        // we need distance_to_cheaper_town cans to reach the next cheaper town, however we already have in_backpack
        // therefore distance_to_cheaper_town-in_backpack is the number of extra cans we want to buy
        // this value can be negative, which is why we max it with 0
        int will_buy = std::max(0, distance_to_cheaper_town - in_backpack);
        
        answer += will_buy * C[i];
        in_backpack += will_buy;

        // we now walk to the next town, eating D[i] cans
        in_backpack -= D[i];
    }

    /* Write the answer to the output file. */
    fprintf(output_file, "%d\n", answer);

    /* Finally, close the input/output files. */
    fclose(input_file);
    fclose(output_file);

    return 0;
}

Full Solution

We can use the same greedy algorithm as subtask 3, but we must improve the time complexity. For each town i, we need to find the closest town after i that is cheaper than town i. This is the slow part of our solution. We can speed it up by utilising the constraint that 1 ≤ Ci ≤ 20 for all i:

The below video shows an example of this algorithm. The number above each line is the distance between successive towns. The number below each line is the total distance from the first town (this is a Prefix Sum). We store these prefix sum values and use them to calculate the distance from each town to the nearest cheaper town. Infinity is used when there is no later town. In code, you could represent this by a sufficiently large constant value (e.g. 1000000 is big enough for this problem).

Click here to go back.