結果
問題 |
No.1301 Strange Graph Shortest Path
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:17:01 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
WA
|
実行時間 | - |
コード長 | 8,466 bytes |
コンパイル時間 | 1,174 ms |
コンパイル使用メモリ | 93,104 KB |
実行使用メモリ | 16,176 KB |
最終ジャッジ日時 | 2025-05-14 13:18:32 |
合計ジャッジ時間 | 8,338 ms |
ジャッジサーバーID (参考情報) |
judge3 / judge5 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 2 |
other | AC * 28 WA * 5 |
ソースコード
#include <iostream> #include <vector> #include <queue> #include <vector> // Required for vector<bool> using namespace std; // Use long long for potentially large path costs as edge weights can be up to 10^9 // and path length up to N-1 edges. Max cost could be around 10^5 * 10^9 = 10^14. typedef long long ll; // Define a constant for infinity, large enough for path costs. // Max possible path cost is roughly N * max(d_i), sum could be twice that. // 2 * 10^5 * 10^9 = 2 * 10^14. Needs to be larger than that. const ll INF = 4e18; // Structure to represent an edge in the graph struct Edge { int to; // Destination vertex of the edge ll cost_c; // Initial cost (cost for the first traversal) ll cost_d; // Changed cost (cost for the second traversal) int id; // Unique identifier for the edge (index from 0 to M-1) }; // Structure for states in Dijkstra's priority queue struct State { ll dist; // Current shortest distance found to this vertex from the source int u; // Vertex identifier // Overload greater-than operator for min-priority queue functionality // The priority queue will prioritize states with smaller distances. bool operator>(const State& other) const { return dist > other.dist; } }; // Global variables for graph representation and Dijkstra state vector<vector<Edge>> adj; // Adjacency list representation of the graph vector<ll> dist; // Stores shortest distances from the source vertex in Dijkstra vector<int> parent_edge; // Stores the ID of the edge used to reach a vertex on the shortest path tree vector<int> parent_node; // Stores the predecessor vertex on the shortest path tree vector<bool> used_in_first_path; // Tracks which edges (by ID) were part of the first shortest path (1 to N) // Dijkstra's algorithm implementation // Parameters: // start_node: The source vertex for this Dijkstra run // N: Total number of vertices in the graph (used for initializing vectors) // M: Total number of edges (used for sizing used_in_first_path vector) // first_run: Boolean flag. If true, this is the first Dijkstra run (1->N), using initial costs c_i. // If false, this is the second run (N->1), using potentially updated costs d_i for edges used in the first run. void dijkstra(int start_node, int N, int M, bool first_run) { // Initialize distances to infinity for all vertices dist.assign(N + 1, INF); // Initialize parent edge IDs and parent nodes to -1 (indicating no parent yet) parent_edge.assign(N + 1, -1); parent_node.assign(N + 1, -1); dist[start_node] = 0; // Distance from the source vertex to itself is 0 // Min-priority queue to manage vertices to visit, ordered by distance priority_queue<State, vector<State>, greater<State>> pq; pq.push({0, start_node}); // Push the starting state onto the queue while (!pq.empty()) { State current = pq.top(); // Get the state (vertex) with the smallest distance pq.pop(); ll d = current.dist; // Current shortest distance to vertex u int u = current.u; // Current vertex u // Optimization: If we've already found a shorter path to u, this state is outdated, skip it. if (d > dist[u]) { continue; } // Iterate through all edges outgoing from the current vertex u for (const auto& edge : adj[u]) { int v = edge.to; // Neighbor vertex ll current_cost; // The cost of traversing this edge (u, v) // Determine the cost of the edge based on the run type (first or second) if (first_run) { // In the first run (path 1 -> N), always use the initial cost c_i current_cost = edge.cost_c; } else { // In the second run (path N -> 1), check if this edge was used in the first path // Check bounds edge.id < used_in_first_path.size() is implicitly handled since edge.id is 0..M-1 if (used_in_first_path[edge.id]) { // If the edge was used in the first path, its cost is now d_i current_cost = edge.cost_d; } else { // If the edge was not used in the first path, its cost remains c_i current_cost = edge.cost_c; } } // Relaxation step: If path through u offers a shorter distance to v // Check dist[u] != INF to avoid potential overflow issues with INF + cost, although technically not needed if start_node is reachable. if (dist[u] != INF && dist[u] + current_cost < dist[v]) { dist[v] = dist[u] + current_cost; // Update the shortest distance to v parent_node[v] = u; // Set u as the parent of v in the shortest path tree parent_edge[v] = edge.id; // Record the ID of the edge used to reach v pq.push({dist[v], v}); // Push the updated state (distance and vertex v) to the priority queue } } } } int main() { // Use fast I/O operations ios_base::sync_with_stdio(false); cin.tie(NULL); int N; // Number of vertices int M; // Number of edges cin >> N >> M; adj.resize(N + 1); // Resize adjacency list to hold N+1 vertices (using 1-based indexing) // Read M edges from input and build the graph for (int i = 0; i < M; ++i) { int u, v; // Vertices connected by the edge ll c, d; // Initial cost c, updated cost d cin >> u >> v >> c >> d; // Add the edge to the adjacency list for both endpoints since graph is undirected // Store the edge ID 'i' with the edge data. adj[u].push_back({v, c, d, i}); adj[v].push_back({u, c, d, i}); } // Initialize the vector tracking used edges. Size M for M edges with IDs 0..M-1. All initialized to false. used_in_first_path.assign(M, false); // --- First Dijkstra Run: 1 to N --- // Compute the shortest path from vertex 1 to vertex N using initial edge costs (c_i). dijkstra(1, N, M, true); ll cost1 = dist[N]; // Store the cost of this shortest path 1 -> N. // Check reachability. The problem guarantees the graph is connected, so N should always be reachable from 1 if N >= 1. if (cost1 == INF) { // This case indicates N is unreachable. Based on problem constraints, this shouldn't happen. // Outputting -1 or appropriate error message might be necessary in a general case. cout << -1 << endl; return 0; } // --- Backtrack to Mark Edges Used in First Path --- // Trace the path back from N to 1 using the parent pointers stored during Dijkstra. // Mark the edges that belong to this shortest path 1 -> N. int curr = N; // Loop continues as long as we haven't reached vertex 1 and the current node has a valid parent. // The check parent_node[curr] != -1 handles the case where N=1 or if somehow the path is broken. while (curr != 1 && parent_node[curr] != -1) { int edge_id = parent_edge[curr]; // Get the ID of the edge used to reach `curr` from its parent. if (edge_id != -1) { // Check if the edge ID is valid. used_in_first_path[edge_id] = true; // Mark this edge as used. curr = parent_node[curr]; // Move to the parent node to continue backtracking. } else { // This case should not happen if N is reachable from 1 and N != 1. break; } } // --- Second Dijkstra Run: N to 1 --- // Compute the shortest path from vertex N back to vertex 1. // The edge costs used in this run depend on whether they were part of the first path (1 -> N). // If an edge was used, its cost is d_i; otherwise, it's c_i. dijkstra(N, N, M, false); ll cost2 = dist[1]; // Store the cost of this shortest path N -> 1. // Check reachability for the return path. Again, guaranteed by connectivity. if (cost2 == INF) { // Similar to the first path, if 1 is unreachable from N, output -1. Should not happen here. cout << -1 << endl; return 0; } // --- Output Result --- // The total minimum cost for the round trip 1 -> N -> 1 is the sum of the costs // calculated from the two Dijkstra runs. cout << cost1 + cost2 << endl; return 0; }