Minimum Cost to Reach City With Discounts

Updated on 11 June, 2025
Minimum Cost to Reach City With Discounts header image

Problem Statement

Imagine a network of cities connected by a series of bi-directional highways, each with an associated toll cost. Given n cities indexed from 0 to n - 1, and a list highways where each element [city1i, city2i, tolli] describes a direct highway linking city city1i with city city2i at a toll cost of tolli, your task is to find the cheapest route from city 0 to city n - 1.

Additionally, you are equipped with a certain number of discounts, as described by the integer discounts. A discount can halve the toll cost (rounded down) for any highway, but you can use each discount only once and cannot apply more than one discount on the same highway. If traveling from city 0 to city n - 1 is not feasible, the function should return -1.

Examples

Example 1

Input:

n = 5
highways = [[0,1,10],[1,2,10],[2,3,10],[3,4,10],[0,2,100]]
discounts = 1

Output:

30

Explanation:

One optimal path is 0 → 1 → 2 → 3 → 4.
Tolls: 10 + 10 + (10//2=5) + 10 = 35 → suboptimal.

Better path: apply the discount to the most expensive segment:
10 + 10 + 10 + (10//2=5) = 30 → minimum possible cost.

Thus, the total cost is 30.

Example 2

Input:

n = 3
highways = [[0,1,5],[1,2,5],[0,2,100]]
discounts = 1

Output:

5

Explanation:

Path 0 → 1 → 2.
Apply discount on one segment: 5 + (5//2=2) = 7.
Alternative path 0 → 2 costs 100//2 = 50 → not optimal.

Thus, the minimum cost is 7.

Example 3

Input:

n = 3
highways = [[0,1,10]]
discounts = 1

Output:

-1

Explanation:

There is no path to city 2.
Return -1.

Constraints

  • 2 <= n <= 1000
  • 1 <= highways.length <= 1000
  • highways[i].length == 3
  • 0 <= city1i, city2i <= n - 1
  • city1i != city2i
  • 0 <= tolli <= 10^5
  • 0 <= discounts <= 500
  • There are no duplicate highways.

Approach and Intuition

Understanding and solving this problem involves the following steps:

  1. Graph Modeling:

    • Model the cities and highways as a graph where:

      • Nodes are the cities.
      • Edges represent highways with weights equal to their toll costs.
  2. Modified Dijkstra’s Algorithm:

    • Use Dijkstra’s algorithm with an extended state to track:

      • Current city.
      • Total toll cost so far.
      • Number of discounts used so far.
    • Use a priority queue to always process the state with the current minimum total cost.

  3. Transitions:

    • For each neighboring highway:

      • Move to the neighbor without applying a discount.
      • Move to the neighbor applying a discount (if you still have discounts left).
    • Track the minimal toll cost for each unique combination of (city, discounts_used).

  4. Early Termination:

    • As soon as city n - 1 is reached in the priority queue, return the current total cost.
  5. Handling Unreachable Cases:

    • If the queue is exhausted without reaching city n - 1, return -1.

Why This Works

  • The classic Dijkstra’s algorithm finds the shortest path in a graph with non-negative edge weights.

  • Here, the presence of discounts introduces additional "dimensions" in the state space:

    • Moving through the graph involves not just tracking the city but also how many discounts you have used.
  • The approach guarantees optimality because we explore states in order of increasing total cost and avoid revisiting worse states.

Time and Space Complexity

  • Time: O(n * discounts * log(n * discounts)) because each state is (city, discounts_used) and each transition costs O(log heap_size).
  • Space: O(n * discounts) for the visited state tracking.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    int findMinCost(int totalCities, vector<vector<int>>& roads, int maxDiscounts) {
        // Building the graph using road information
        vector<vector<pair<int, int>>> adjacencyList(totalCities);
        for (auto &road : roads) {
            int city1 = road[0], city2 = road[1], cost = road[2];
            adjacencyList[city1].emplace_back(city2, cost);
            adjacencyList[city2].emplace_back(city1, cost);
        }

        // Priority queue to manage state exploration based on (totalCost, currentCity, discountsUsed)
        priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> minHeap;
        minHeap.emplace(0, 0, 0);  // Start from city 0 with no cost using 0 discounts

        // Table to store the minimum cost to reach each city using up to a certain number of discounts
        vector<vector<int>> costToReach(totalCities, vector<int>(maxDiscounts + 1, INT_MAX));
        costToReach[0][0] = 0;

        while (!minHeap.empty()) {
            auto [currentCost, currentCity, discountsUsed] = minHeap.top();
            minHeap.pop();

            // Ignore this state if it's not optimal
            if (currentCost > costToReach[currentCity][discountsUsed]) continue;

            // Check all possible moves from the current city
            for (auto &[nextCity, roadCost] : adjacencyList[currentCity]) {
                // Normal move without using a discount
                if (costToReach[nextCity][discountsUsed] > currentCost + roadCost) {
                    costToReach[nextCity][discountsUsed] = currentCost + roadCost;
                    minHeap.emplace(costToReach[nextCity][discountsUsed], nextCity, discountsUsed);
                }

                // Move using a discount if any remain
                if (discountsUsed < maxDiscounts) {
                    int reducedCost = currentCost + roadCost / 2;
                    if (costToReach[nextCity][discountsUsed + 1] > reducedCost) {
                        costToReach[nextCity][discountsUsed + 1] = reducedCost;
                        minHeap.emplace(reducedCost, nextCity, discountsUsed + 1);
                    }
                }
            }
        }

        // Determine the minimal cost to reach the last city with any discount level
        int leastCost = *min_element(costToReach[totalCities - 1].begin(), costToReach[totalCities - 1].end());
        return leastCost == INT_MAX ? -1 : leastCost;
    }
};

The C++ function findMinCost is designed to solve the problem of finding the minimum cost to reach the last city from the first city across a set of cities connected by roads, with the option to use a limited number of discounts. Each discount halves the cost of a road but can only be used a limited number of times.

Here's a succinct breakdown of the implementation:

  • Graph Representation: The solution starts by constructing an adjacency list to represent the cities and the roads between them. Each city and its corresponding roads are stored along with the travel cost.

  • Priority Queue: A priority queue (min heap) is utilized to efficiently explore the lowest cost paths first. Each state in the priority queue contains the current total cost to reach a city, the city number, and the number of discounts used.

  • Cost Array: A 2D vector (costToReach) is initialized to store the minimum costs to reach each city using up to a certain number of discounts. All initial costs are set to INT_MAX reflecting an unreached state, except for the starting city with no discounts.

  • Exploration of Paths: While the priority queue is not empty:

    1. Extract the state with the lowest cost.
    2. Skip any state that is not optimal.
    3. For each connected city:
      • Update the cost for reaching the city without using an additional discount if this path offers a lower cost.
      • If possible (discounts remain), compute the reduced cost with an additional discount and update accordingly.
  • Result Calculation: After exploring all possible paths, the solution determines the minimum cost to reach the last city considering any discount level. If no valid path could offer a cost, it returns -1.

This function optimally handles the problem using Dijkstra-like behavior with a priority queue, combined with additional logic to manage discounted travel costs. The algorithm ensures that all potential cheaper paths are considered by maintaining and updating an optimal cost for reaching each city with a certain number of discounts used, thereby aiming to find the minimal travel cost to the final destination.

java
class Solution {

    public int findMinCost(int cities, int[][] roads, int maxDiscounts) {
        // Generate the map using the roads data
        List<List<int[]>> cityMap = new ArrayList<>();
        for (int i = 0; i < cities; ++i) {
            cityMap.add(new ArrayList<>());
        }
        for (int[] road : roads) {
            int from = road[0], to = road[1], cost = road[2];
            cityMap.get(from).add(new int[] { to, cost });
            cityMap.get(to).add(new int[] { from, cost });
        }

        // Priority queue to manage exploring paths
        PriorityQueue<int[]> pathQueue = new PriorityQueue<>(
            Comparator.comparingInt(a -> a[0])
        );
        pathQueue.offer(new int[] { 0, 0, 0 }); // Start at city 0 with no cost and no discounts used

        // Distance tracker for each city based on discount usage
        int[][] costs = new int[cities][maxDiscounts + 1];
        for (int[] costRow : costs) {
            Arrays.fill(costRow, Integer.MAX_VALUE);
        }
        costs[0][0] = 0;

        while (!pathQueue.isEmpty()) {
            int[] currentPath = pathQueue.poll();
            int cost = currentPath[0], currentCity = currentPath[1], usedDiscounts =
                currentPath[2];

            // Skip paths not optimal
            if (cost > costs[currentCity][usedDiscounts]) {
                continue;
            }

            // Traverse neighbors
            for (int[] neighbor : cityMap.get(currentCity)) {
                int adjacentCity = neighbor[0], pathCost = neighbor[1];

                // Explore without discount
                if (cost + pathCost < costs[adjacentCity][usedDiscounts]) {
                    costs[adjacentCity][usedDiscounts] = cost + pathCost;
                    pathQueue.offer(
                        new int[] {
                            costs[adjacentCity][usedDiscounts],
                            adjacentCity,
                            usedDiscounts,
                        }
                    );
                }

                // Explore using discount if available
                if (usedDiscounts < maxDiscounts) {
                    int discountedCost = cost + pathCost / 2;
                    if (
                        discountedCost < costs[adjacentCity][usedDiscounts + 1]
                    ) {
                        costs[adjacentCity][usedDiscounts + 1] = discountedCost;
                        pathQueue.offer(
                            new int[] {
                                discountedCost,
                                adjacentCity,
                                usedDiscounts + 1,
                            }
                        );
                    }
                }
            }
        }

        // Determine the minimal cost to reach the last city considering any discount count
        int lowestCost = Integer.MAX_VALUE;
        for (int d = 0; d <= maxDiscounts; ++d) {
            lowestCost = Math.min(lowestCost, costs[cities - 1][d]);
        }
        return lowestCost == Integer.MAX_VALUE ? -1 : lowestCost;
    }
}

This solution in Java finds the minimum cost to travel from the first city to the last city using a given number of discounts over a set of cities connected by roads with specific costs.

  • Maintain a list of lists cityMap containing the roads and associated costs between cities.
  • Implement a priority queue pathQueue to explore the least costly paths first. The path initially includes the starting city, cost, and number of discounts used.
  • Use a 2D array costs to track the cheapest known path costs to every city with varying discounts used. If a path is not optimal, skip to the next iteration.
  • Iterate over each city's neighbors:
    • Explore the next accessible city without using a discount.
    • If discounts are available, also explore the cost using the discount.
  • After exploring all possibilities, determine the minimum cost to reach the last city using any number of discounts.

This approach uses a priority queue for efficient path selection and dynamic programming techniques for minimizing travel costs with or without applying discounts. If no path to the final city is feasible within the constraints, the method returns -1. This solution ensures a balance between cost efficiency and complexity management, making it effective for problems involving route optimization with conditional discounts.

python
class Solution:
    def findMinimumTravelCost(
        self, cities: int, roads: List[List[int]], discount_limit: int
    ) -> int:
        # Building the road map
        road_map = [[] for _ in range(cities)]
        for road in roads:
            start, end, cost = road
            road_map[start].append((end, cost))
            road_map[end].append((start, cost))

        # Priority queue to maintain state with (travel_cost, current_city, discounts_applied)
        pq = [(0, 0, 0)]  # Starting from city 0 at cost 0 using 0 discounts

        # Tracking minimum distances achievable per city based on discounts applied
        min_dist = [[float("inf")] * (discount_limit + 1) for _ in range(cities)]
        min_dist[0][0] = 0

        while pq:
            curr_cost, current_city, used_discounts = heapq.heappop(pq)

            # Ignore this state if we have found a better state earlier
            if curr_cost > min_dist[current_city][used_discounts]:
                continue

            # Check all possible movements from the current_city
            for adj_city, road_cost in road_map[current_city]:
                # Move to adjacent city without using an additional discount
                normal_cost = curr_cost + road_cost
                if normal_cost < min_dist[adj_city][used_discounts]:
                    min_dist[adj_city][used_discounts] = normal_cost
                    heapq.heappush(
                        pq,
                        (normal_cost, adj_city, used_discounts),
                    )

                # Move to adjacent city using a discount if possible
                if used_discounts < discount_limit:
                    discounted_cost = curr_cost + road_cost // 2
                    if discounted_cost < min_dist[adj_city][used_discounts + 1]:
                        min_dist[adj_city][used_discounts + 1] = discounted_cost
                        heapq.heappush(
                            pq,
                            (discounted_cost, adj_city, used_discounts + 1),
                        )

        # Determine the minimum cost to reach the last city using any number of discounts
        min_travel_cost = min(min_dist[cities - 1])
        return -1 if min_travel_cost == float("inf") else min_travel_cost

This Python program addresses the problem of finding the minimum cost to reach the final city in a given network of cities and roads, with the ability to use a specified number of discounts that halve the cost of any road. The solution uses a graph traversal approach in combination with a priority queue to efficiently determine the least expensive travel path.

In the solution:

  • The program initially constructs a road_map to represent the cities as the nodes of a graph and the road connections with their costs as the edges.
  • A priority queue (a min-heap) and a distance table min_dist are utilized. The priority queue holds tuples consisting of the current travel cost, the current city, and the number of discounts used. The min_dist list stores the minimum cost needed to reach each city for each number of discounts applied.
  • The process iteratively examines the least expensive travel option from the priority queue, exploring adjacent cities and considering travel with or without using a discount.
  • For each adjacent city, if the usual travel cost is lower than the recorded minimum, or if using a discount provides a cost advantage and the limit on discounts has not been reached, the city is revisited with the updated cost and discount count.
  • The algorithm continues until all potential city-price-discount combinations have been considered or the queue is empty.
  • The final segment of the code identifies the lowest cost required to reach the last city, taking into account any number of used discounts, determining if the destination is reachable.

The program makes effective use of the heapq library for the priority queue to always process the currently known cheapest path option, ensuring an optimal solution is found. With careful management of cities, costs, and discounts within the priority structure, the implementation guarantees that all possible pathways, both discounted and non-discounted, are fully evaluated.

This solution efficiently handles scenarios where connections between cities allow multiple discount opportunities, ensuring the complexity is manageable and geared towards finding the most cost-effective travel options.

Comments

No comments yet.