#include #include #include #include #include #include #include #include #include #include #include // Use a simple struct for clarity struct Point { int x, y, z; // Default spaceship operator for easy comparisons if needed auto operator<=>(const Point &) const = default; }; struct Edge { int u; // Index of first point int v; // Index of second point double dist; }; // --- Modern Union-Find (DSU) Implementation --- struct DSU { std::vector parent; int groups; explicit DSU(size_t n) : parent(n), groups(static_cast(n)) { // Fill with 0, 1, 2, ..., n-1 std::iota(parent.begin(), parent.end(), 0); } // Find with Path Compression // Returns the representative ID of the set containing i auto find(int i) -> int { if (parent[i] == i) { return i; } return parent[i] = find(parent[i]); } // Unite two sets. Returns true if they were different sets. auto unite(int i, int j) -> bool { int root_i = find(i); int root_j = find(j); if (root_i != root_j) { parent[root_i] = root_j; // Link roots groups--; return true; } return false; } }; // --- Helper Functions --- static auto getDistance(const Point &a, const Point &b) -> double { // std::hypot is cleaner (C++17) and avoids manual pow/sqrt // For 3D: hypot(x, y, z) is C++17 specific overload return std::hypot(a.x - b.x, a.y - b.y, a.z - b.z); } static auto parseInput(std::string_view filename) -> std::expected, std::string> { std::ifstream inputF{std::string(filename)}; if (!inputF) { return std::unexpected{"Could not open file."}; } std::vector points; std::string line; while (std::getline(inputF, line)) { if (line.empty()) { continue; } // Modern splitting and parsing auto parts = line | std::views::split(',') | std::views::transform([](auto &&rng) -> auto { // Convert range to string for stoi (C++23 common_range fix) return std::string(rng.begin(), rng.end()); }) | std::ranges::to>(); if (parts.size() >= 3) { points.emplace_back(std::stoi(parts[0]), std::stoi(parts[1]), std::stoi(parts[2])); } } return points; } static auto generateSortedEdges(const std::vector &points) -> std::vector { std::vector edges; edges.reserve((points.size() * (points.size() - 1)) / 2); // Standard triangular loop is still clearer/faster than range combinatorics here for (size_t i = 0; i < points.size(); ++i) { for (size_t j = i + 1; j < points.size(); ++j) { edges.push_back( {.u = static_cast(i), .v = static_cast(j), .dist = getDistance(points[i], points[j])}); } } // Modern Sort with Projections // Sorts by the 'dist' member automatically std::ranges::sort(edges, {}, &Edge::dist); return edges; } // --- Main Logic --- static auto solvePartTwo(const std::vector &points) -> long long { auto edges = generateSortedEdges(points); DSU dsu(points.size()); for (const auto &[u, v, dist] : edges) { // Try to unite the two points if (dsu.unite(u, v)) { // If this connection reduced us to exactly 1 group, we are done! if (dsu.groups == 1) { std::println("Connected all points! Last edge: {} <-> {}", u, v); return (long long)points[u].x * (long long)points[v].x; } } } return -1; // Should not happen if graph is connected } auto main() -> int { // Usage of std::string_view literal auto puzzle = parseInput("puzzle_input"); if (!puzzle) { std::println(stderr, "Error: {}", puzzle.error()); return 1; } std::println("Loaded {} points.", puzzle->size()); // Part 2 Logic auto result = solvePartTwo(*puzzle); std::println("Part 2 Result: {}", result); return 0; }