Here, we will discuss the solution to Backpacking from AIO 2024.
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:
# Read the values of N, K, D, and C.
N, K = map(int, input().strip().split())
D = list(map(int, input().strip().split()))
C = list(map(int, input().strip().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.
print(answer)
#include <cstdio>
#include <algorithm>
int N, K, D[200005], C[200005];
int main(void) {
/* Read the values of N, K, D, and C. */
scanf("%d%d", &N, &K);
for (int i = 0; i < N-1; i++) {
scanf("%d", &D[i]);
}
for (int i = 0; i < N; i++) {
scanf("%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. */
printf("%d\n", answer);
}
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.
# Read the values of N, K, D, and C.
N, K = map(int, input().strip().split())
D = list(map(int, input().strip().split()))
C = list(map(int, input().strip().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.
print(answer)
#include <cstdio>
#include <algorithm>
int N, K, D[200005], C[200005];
int main(void) {
/* Read the values of N, K, D, and C. */
scanf("%d%d", &N, &K);
for (int i = 0; i < N-1; i++) {
scanf("%d", &D[i]);
}
for (int i = 0; i < N; i++) {
scanf("%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. */
printf("%d\n", answer);
}
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?
# 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 = []
# Read the values of N, K, D, and C.
N, K = map(int, input().strip().split())
D = list(map(int, input().strip().split()))
C = list(map(int, input().strip().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.
print(answer)
#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) {
/* Read the values of N, K, D, and C. */
scanf("%d%d", &N, &K);
for (int i = 0; i < N-1; i++) {
scanf("%d", &D[i]);
}
for (int i = 0; i < N; i++) {
scanf("%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. */
printf("%d\n", answer);
return 0;
}
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: