結果
問題 |
No.1069 電柱 / Pole (Hard)
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:15:02 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 338 ms / 2,000 ms |
コード長 | 16,201 bytes |
コンパイル時間 | 1,888 ms |
コンパイル使用メモリ | 161,004 KB |
実行使用メモリ | 11,520 KB |
最終ジャッジ日時 | 2025-05-14 13:16:08 |
合計ジャッジ時間 | 5,348 ms |
ジャッジサーバーID (参考情報) |
judge4 / judge3 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 4 |
other | AC * 79 |
ソースコード
#include <iostream> #include <vector> #include <cmath> #include <queue> #include <iomanip> #include <set> #include <map> #include <limits> #include <vector> #include <algorithm> // For reverse, min, max #include <functional> // For greater template specialization #include <set> // For std::set using namespace std; // Use double for floating point calculations. It provides sufficient precision for this problem. using Real = double; const Real INF = numeric_limits<Real>::infinity(); // Epsilon for floating point comparisons. Used to handle potential precision issues. const Real EPS = 1e-9; // State structure for Dijkstra's algorithm priority queue struct State { Real cost; // Current path cost to this node int node; // Current node ID // Comparison operator for min-heap. Priority queue extracts minimum cost state. bool operator>(const State& other) const { // If costs are nearly equal, use node ID as a tie-breaker for deterministic behavior. // This isn't strictly necessary for correctness but can help consistency. if (abs(cost - other.cost) < EPS) return node > other.node; // Otherwise, compare costs. Lower cost has higher priority. return cost > other.cost; } }; // Structure representing a path found by the algorithm struct Path { Real cost; // Total cost of the path vector<int> nodes; // Sequence of nodes in the path // Comparison operator for the priority queue of candidate paths (min-heap). bool operator>(const Path& other) const { // If costs are nearly equal, use node sequence lexicographically for tie-breaking. // This ensures paths with same cost are ordered consistently. if (abs(cost - other.cost) < EPS) { return nodes > other.nodes; } // Otherwise, compare costs. Lower cost path has higher priority. return cost > other.cost; } // Less than operator overload, useful for sorting or using in containers like std::set. bool operator<(const Path& other) const { if (abs(cost - other.cost) > EPS) { // Check difference against epsilon return cost < other.cost; } // If costs are nearly equal, tie-break using node sequence. return nodes < other.nodes; } // Equality operator overload might be useful. bool operator==(const Path& other) const { // Check cost equality within epsilon and node sequence equality. return abs(cost - other.cost) < EPS && nodes == other.nodes; } }; // Global variables int N; // Number of poles (nodes) vector<pair<Real, Real>> coords; // Coordinates of poles (nodes) vector<vector<pair<int, Real>>> adj; // Adjacency list representation of the graph: stores pairs {neighbor_node, edge_weight} /** * Dijkstra's algorithm implementation. * Finds the shortest path from start node to end node in the graph. * Considers dynamically removed nodes and edges specified by the sets. * This is used within Yen's algorithm to find spur paths. * * @param start The starting node ID. * @param end The target node ID. * @param removed_nodes A set of node IDs that should be ignored during pathfinding. * @param removed_edges A set of edges (represented as pairs of node IDs) that should be ignored. Edges are stored canonically {min(u,v), max(u,v)}. * @return A pair containing the cost of the shortest path and the sequence of nodes in the path. Returns {INF, {}} if no path exists. */ pair<Real, vector<int>> dijkstra(int start, int end, const set<int>& removed_nodes, const set<pair<int, int>>& removed_edges) { // Priority queue for Dijkstra, stores {cost, node} states. Uses min-heap based on cost. priority_queue<State, vector<State>, greater<State>> pq; // Distance vector, initialized to infinity for all nodes. vector<Real> d(N + 1, INF); // Parent pointer vector, used to reconstruct the path. Initialized to -1. vector<int> p(N + 1, -1); // Initialize distance to start node as 0. d[start] = 0.0; pq.push({0.0, start}); // Main loop of Dijkstra's algorithm while (!pq.empty()) { State current = pq.top(); pq.pop(); int u = current.node; Real current_cost = current.cost; // If we pulled a state with cost higher than already known shortest path cost to u, skip it. // Use epsilon comparison for robustness with floating point numbers. if (current_cost > d[u] + EPS) { continue; } // Optimization: If we extract the target node, we have found the shortest path to it. Can break early. if (u == end) { break; } // Explore neighbors of the current node u for (const auto& edge : adj[u]) { int v = edge.first; // Neighbor node Real weight = edge.second; // Edge weight // Check if the neighbor node v is in the set of removed nodes. if (removed_nodes.count(v)) { continue; // Skip this neighbor if it's removed. } // Check if the edge (u, v) is in the set of removed edges. // Use canonical representation {min(u, v), max(u, v)} for the edge lookup. pair<int, int> current_edge = {min(u, v), max(u, v)}; if (removed_edges.count(current_edge)) { continue; // Skip this edge if it's removed. } // Calculate the cost to reach neighbor v through u. Real new_cost = current_cost + weight; // If the new path to v is shorter than the currently known shortest path to v. // Use epsilon comparison for floating point robustness. if (new_cost < d[v] - EPS) { // Update distance to v. d[v] = new_cost; // Set u as the parent of v in the shortest path tree. p[v] = u; // Push the new state {new_cost, v} to the priority queue. pq.push({new_cost, v}); } } } // After Dijkstra finishes, check if the end node was reached. if (d[end] == INF) { // If distance to end node is still infinity, no path was found. return {INF, {}}; } // Reconstruct the shortest path from end node back to start node using parent pointers. vector<int> path_nodes; int curr = end; while (true) { path_nodes.push_back(curr); if (curr == start) break; // Stop when we reach the start node. int parent_node = p[curr]; // Get the parent node. // Error check: if parent is -1 before reaching start, path reconstruction failed. if (parent_node == -1) { return {INF, {}}; // Return indication of failure. } curr = parent_node; // Move to the parent node. } // The path is reconstructed backwards, so reverse it to get start-to-end order. reverse(path_nodes.begin(), path_nodes.end()); // Basic validation: Check if the reconstructed path is valid (starts at start, ends at end). // Handle the special case where start == end. if (start == end) { if (abs(d[end] - 0.0) < EPS) return {0.0, {start}}; // Correct path is just [start] with cost 0. else return {INF, {}}; // Should not happen if start == end and graph is valid. } // General validation for start != end. if (path_nodes.empty() || path_nodes.front() != start || path_nodes.back() != end) { return {INF, {}}; // Return indication of invalid path. } // Return the shortest path cost and the sequence of nodes. return {d[end], path_nodes}; } int main() { // Use fast I/O operations. ios_base::sync_with_stdio(false); cin.tie(NULL); int M, K; // M: number of potential connections, K: number of paths to find cin >> N >> M >> K; int X, Y; // Start node (power station) X, End node (castle) Y cin >> X >> Y; // Read coordinates for each pole (node). Coordinates are 1-indexed. coords.resize(N + 1); for (int i = 1; i <= N; ++i) { cin >> coords[i].first >> coords[i].second; } // Read potential connections (edges) and build the adjacency list. adj.resize(N + 1); for (int i = 0; i < M; ++i) { int u, v; cin >> u >> v; // Calculate Euclidean distance between poles u and v. Real weight = sqrt(pow(coords[u].first - coords[v].first, 2) + pow(coords[u].second - coords[v].second, 2)); // Add edge to adjacency list for both directions since connections are bidirectional. adj[u].push_back({v, weight}); adj[v].push_back({u, weight}); } // Vector `A` stores the K shortest paths found so far. vector<Path> A; // Priority queue `B` stores candidate paths, ordered by cost (min-heap). priority_queue<Path, vector<Path>, greater<Path>> B; // Set `found_paths_nodes` stores the node sequences of paths already added to `A`. // This prevents adding the same path sequence multiple times. set<vector<int>> found_paths_nodes; // Find the initial shortest path from X to Y using Dijkstra without any removals. pair<Real, vector<int>> first_path_info = dijkstra(X, Y, {}, {}); // If a path exists, add it to the candidate priority queue B. if (first_path_info.first != INF) { B.push({first_path_info.first, first_path_info.second}); } // Vector `result_costs` will store the costs of the K shortest paths found. vector<Real> result_costs; // Main loop of Yen's K-shortest paths algorithm. // Continues as long as we need more paths (result_costs.size() < K) and there are candidates in B. while(result_costs.size() < K && !B.empty()) { // Extract the path with the minimum cost from the candidate queue B. Path current_shortest = B.top(); B.pop(); // Check if this path's node sequence has already been found and added to results. // This check handles cases where Yen's algorithm might generate the same path structure via different spur nodes. if(found_paths_nodes.count(current_shortest.nodes)) { continue; // Skip this path if its node sequence is already processed. } // This is a new shortest path. Add it to the list A and record its cost. A.push_back(current_shortest); result_costs.push_back(current_shortest.cost); // Mark this path's node sequence as found. found_paths_nodes.insert(current_shortest.nodes); // If we have found K paths, terminate the loop. if (result_costs.size() == K) break; // Get the node sequence of the path just found (k-th shortest path). vector<int>& P_k_nodes = current_shortest.nodes; // Iterate through each node `spur_node` in the path P_k (except the last node). // Each node `P_k_nodes[i]` serves as a potential "spur node" where a new path can branch off. for (int i = 0; i < P_k_nodes.size() - 1; ++i) { int spur_node = P_k_nodes[i]; // The node where the path will deviate. // The "root path" is the part of P_k from the start node X up to the spur node. vector<int> root_path_nodes; Real root_path_cost = 0; // Calculate the cost of the root path. for(int j=0; j<=i; ++j) { root_path_nodes.push_back(P_k_nodes[j]); if(j > 0) { // Find the weight of the edge between P_k_nodes[j-1] and P_k_nodes[j]. Real edge_w = -1.0; // Initialize with sentinel value. for(auto& edge : adj[P_k_nodes[j-1]]) { if(edge.first == P_k_nodes[j]) { edge_w = edge.second; break; } } // Basic check: edge weight must be non-negative. if (edge_w < 0.0) { /* Error: Invalid path or graph data */ } root_path_cost += edge_w; // Add edge weight to root path cost. } } // Prepare sets of nodes and edges to be temporarily removed for finding the spur path. // Removed nodes: All nodes in the root path *before* the spur node. This prevents cycles. set<int> current_removed_nodes; for (int j = 0; j < i; ++j) { current_removed_nodes.insert(P_k_nodes[j]); } // Removed edges: Edges starting from the spur node that belong to any previously found shortest path (in A) // which shares the same root path as P_k up to the spur node. This forces the new path to deviate. set<pair<int, int>> current_removed_edges; // Iterate through all paths already confirmed shortest (stored in A). for(const auto& path_info : A) { const vector<int>& path_nodes = path_info.nodes; // Nodes of a previously found path. // Check if this path is long enough to potentially share the prefix and have an edge after the spur node. if (path_nodes.size() > i + 1) { bool same_prefix = true; // Compare nodes up to index i (the spur node index). for(int j=0; j<=i; ++j) { if (path_nodes[j] != P_k_nodes[j]) { same_prefix = false; // Prefixes differ. break; } } // If the prefixes match up to the spur node: if (same_prefix) { int u1 = path_nodes[i]; // The spur node (same as P_k_nodes[i]). int v1 = path_nodes[i+1]; // The node immediately following the spur node in this path. // Add the edge (u1, v1) to the set of removed edges for the next Dijkstra call. // Use canonical representation {min(u1, v1), max(u1, v1)} for the edge. current_removed_edges.insert({min(u1, v1), max(u1, v1)}); } } } // Find the shortest path from the spur node to the target node Y using Dijkstra, // considering the temporarily removed nodes and edges. This is the "spur path". pair<Real, vector<int>> spur_path_info = dijkstra(spur_node, Y, current_removed_nodes, current_removed_edges); // If a valid spur path is found (cost is not INF and path is not empty): if (spur_path_info.first != INF && !spur_path_info.second.empty()) { // Construct the complete new path by concatenating the root path and the spur path. vector<int> total_path_nodes = root_path_nodes; // Append the spur path nodes, excluding the first node (spur_node itself, already in root_path_nodes). total_path_nodes.insert(total_path_nodes.end(), spur_path_info.second.begin() + 1, spur_path_info.second.end()); // Calculate the total cost of this new path. Real total_cost = root_path_cost + spur_path_info.first; // Add this newly constructed path to the candidate priority queue B, // but only if its node sequence has not been found previously. if (!found_paths_nodes.count(total_path_nodes)) { B.push({total_cost, total_path_nodes}); } } } } // After the loop finishes, output the costs of the found shortest paths. cout << fixed << setprecision(10); // Set output precision for floating point numbers. for (Real cost : result_costs) { cout << cost << "\n"; } // If fewer than K paths were found, output -1 for the remaining requested paths. for (int i = result_costs.size(); i < K; ++i) { cout << -1 << "\n"; } return 0; }