Finding the Shortest Path with Time-Based Road Closures using Dijkstra's Algorithm

This problem involves finding the shrotest path in a graph where certain edges are temporarily closed. The graph has $N$ nodes and $M$ edges, with $N \le 1000$ and $M \le 10000$. Given the constraints, an adjacency matrix is a suitable choice for representing the graph.

We need to determine the optimal travel time for a character, let's call them Luka. The constraint is that a Mr. T travels along a predefined path, closing roads for a specific duration. We need to record the closure time for each road segment.

First, let's process Mr. T's movement. Suppose Mr. T travels through a sequence of locations $c_1, c_2, \dots, c_g$. The time taken for each segment $c_i$ to $c_{i+1}$ is given by $f[c_i][c_{i+1}]$. Mr. T starts at time 0. For each segment $(c_i, c_{i+1})$, he takes $f[c_i][c_{i+1}]$ time to traverse it. During this time, the road is effectively closed to others.

We can maintain a current time variable, currentTime, initialized to 0. As Mr. T traverses from $c_i$ to $c_{i+1}$, currentTime increases by $f[c_i][c_{i+1}]$. We can store the start time of the closure for the edge $(u, v)$ in a 2D array closureStartTime[u][v]. Since the road is bidirectional, we should also record closureStartTime[v][u]. The closure starts at currentTime and lasts for $f[c_i][c_{i+1}]$ duration.

After pre-calculating the road closure times, we can use Dijkstra's algorithm to find the shortest path from a starting node $A$ to a destination node $B$. The initial time of departure for Luka is given as $K$. We need to modify the standard Dijkstra's algorithm to account for the time-based road closures.

When considering a edge $(u, v)$ from node $u$ to node $v$, if Luka arrives at node $u$ at time arrivalTimeAtU, he can proceed to node $v$ only if the road is not closed. The road $(u, v)$ is closed from closureStartTime[u][v] to closureStartTime[u][v] + f[u][v] - 1. If Luka arrives at $u$ at arrivalTimeAtU and intends to travel to $v$ which takes $f[u][v]$ time:

  1. If arrivalTimeAtU is strictly before closureStartTime[u][v], Luka can pass through without delay. The arrival time at $v$ will be arrivalTimeAtU + f[u][v].
  2. If arrivalTimeAtU falls within the closure period (i.e., closureStartTime[u][v] <= arrivalTimeAtU <= closureStartTime[u][v] + f[u][v] - 1), Luka must wait until the road reopens. The earliest he can start traversing is closureStartTime[u][v] + f[u][v]. Therefore, the arrival time at $v$ will be closureStartTime[u][v] + f[u][v] + f[u][v].
  3. If arrivalTimeAtU is after the closure period ends, Luka can pass through immediately. The arrival time at $v$ will be arrivalTimeAtU + f[u][v].

A more concise way to express the waiting logic: If Luka arrives at $u$ at arrivalTimeAtU and the edge $(u, v)$ is under closure starting at closureStartTime[u][v] and lasting $f[u][v]$: the earliest Luka can start traversing the edge to $v$ is max(arrivalTimeAtU, closureStartTime[u][v] + f[u][v]). The arrival time at $v$ is then earliestStartTime + f[u][v]. This simplifies to: if arrivalTimeAtU >= closureStartTime[u][v], the departure time from $u$ is max(arrivalTimeAtU, closureStartTime[u][v] + f[u][v]). Otherwise, departure is arrivalTimeAtU. This is slightly incorrect. Let's refine.

Corrected logic for traversing edge $(u, v)$ when Luka arrives at $u$ at arrivalTimeAtU:

Let closureStart = closureStartTime[u][v] and travelTime = f[u][v]. If arrivalTimeAtU < closureStart: Arrival at $v$ is arrivalTimeAtU + travelTime. Else if arrivalTimeAtU >= closureStart && arrivalTimeAtU < closureStart + travelTime: Luka must wait until the road reopens. He can start crossing at closureStart + travelTime. So, arrival at $v$ is (closureStart + travelTime) + travelTime. Else (arrivalTimeAtU >= closureStart + travelTime): Luka can cross immediately. Arrival at $v$ is arrivalTimeAtU + travelTime.

This logic can be encapsulated in a drive function.

Dijkstra's algorithm will use a priority queue storing pairs of (current node, arrival time). The distance array dis[i] will store the earliest arrival time at node $i$. Initialize dis to infinity, set dis[A] = K, and push {A, K} into the priority queue. When extracting a node u with arrival time t, iterate through its neighbors v. Calculate the arrival time at v using the drive function and update dis[v] if a shorter path is found, pushing {v, dis[v]} into the priority queue.

Finally, the result is dis[B] - K, as the initial departure time $K$ should not be part of the travel duration.

#include <iostream>
#include <vector>
#include <queue>
#include <limits>

const int INF = std::numeric_limits<int>::max();
const int MAXN = 1005;

int travel_time[MAXN][MAXN];
int closure_start_time[MAXN][MAXN];
int earliest_arrival[MAXN];
bool visited[MAXN];

struct State {
    int node;
    int time;

    bool operator>(const State& other) const {
        return time > other.time;
    }
};

int calculate_arrival(int u, int v, int arrival_at_u) {
    int travel_duration = travel_time[u][v];
    int closure_start = closure_start_time[u][v];
    int closure_end = closure_start + travel_duration;

    if (arrival_at_u < closure_start) {
        return arrival_at_u + travel_duration;
    } else if (arrival_at_u >= closure_start && arrival_at_u < closure_end) {
        // Must wait until closure ends, then traverse
        return closure_end + travel_duration;
    } else { // arrival_at_u >= closure_end
        return arrival_at_u + travel_duration;
    }
}

void dijkstra(int start_node, int num_nodes, int initial_time) {
    for (int i = 1; i <= num_nodes; ++i) {
        earliest_arrival[i] = INF;
        visited[i] = false;
    }

    earliest_arrival[start_node] = initial_time;
    std::priority_queue<State, std::vector<State>, std::greater<State>> pq;
    pq.push({start_node, initial_time});

    while (!pq.empty()) {
        State current = pq.top();
        pq.pop();

        int u = current.node;
        int current_time = current.time;

        if (visited[u]) continue;
        visited[u] = true;

        for (int v = 1; v <= num_nodes; ++v) {
            if (travel_time[u][v] > 0) { // If there's an edge
                int arrival_at_v = calculate_arrival(u, v, current_time);

                if (arrival_at_v < earliest_arrival[v]) {
                    earliest_arrival[v] = arrival_at_v;
                    pq.push({v, earliest_arrival[v]});
                }
            }
        }
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n, m, start_node, end_node, initial_departure_time, num_closures;
    std::cin >> n >> m >> start_node >> end_node >> initial_departure_time >> num_closures;

    std::vector<int> closure_path(num_closures);
    for (int i = 0; i < num_closures; ++i) {
        std::cin >> closure_path[i];
    }

    for (int i = 0; i < m; ++i) {
        int u, v, weight;
        std::cin >> u >> v >> weight;
        travel_time[u][v] = weight;
        travel_time[v][u] = weight;
    }

    int current_time_tracker = 0;
    for (int i = 0; i < num_closures - 1; ++i) {
        int u = closure_path[i];
        int v = closure_path[i+1];
        int segment_duration = travel_time[u][v];
        
        closure_start_time[u][v] = current_time_tracker;
        closure_start_time[v][u] = current_time_tracker; // Bidirectional closure
        
        current_time_tracker += segment_duration;
    }

    dijkstra(start_node, n, initial_departure_time);

    // The result is the total elapsed time, so subtract the initial departure time.
    // If end_node is unreachable, earliest_arrival[end_node] will be INF.
    // The problem statement implies reachability, so we proceed.
    if (earliest_arrival[end_node] == INF) {
        // Handle unreachable case if necessary, e.g., print -1
    } else {
        std::cout << earliest_arrival[end_node] - initial_departure_time << std::endl;
    }

    return 0;
}

Tags: graph Dijkstra Shortest Path time constraints algorithm

Posted on Tue, 28 Jul 2026 17:09:19 +0000 by lar5