結果

問題 No.2436 Min Diff Distance
ユーザー qwewe
提出日時 2025-05-14 13:03:13
言語 C++17
(gcc 13.3.0 + boost 1.87.0)
結果
AC  
実行時間 329 ms / 2,000 ms
コード長 11,624 bytes
コンパイル時間 1,171 ms
コンパイル使用メモリ 100,448 KB
実行使用メモリ 24,516 KB
最終ジャッジ日時 2025-05-14 13:04:32
合計ジャッジ時間 6,902 ms
ジャッジサーバーID
(参考情報)
judge1 / judge4
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 23
権限があれば一括ダウンロードができます

ソースコード

diff #

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <limits>
#include <numeric> // For std::iota

// Structure to represent a point in original and transformed coordinates
struct Point {
    long long x, y; // Original coordinates
    long long s, d; // Transformed coordinates: s = x+y, d = x-y
    int id; // Original index (0 to N-1)
};

// Calculate Chebyshev distance between two points using their transformed coordinates
// This is equivalent to the Manhattan distance between the points in original coordinates.
long long chebyshev_dist(const Point& p1, const Point& p2) {
    // The formula is max(|s1 - s2|, |d1 - d2|)
    return std::max(std::abs(p1.s - p2.s), std::abs(p1.d - p2.d));
}

// Node structure for the K-D tree
struct KDNode {
    int point_idx; // Index into the original 'points' vector
    KDNode *left = nullptr, *right = nullptr;
    // Bounding box for the subtree rooted at this node, using transformed coordinates (s, d)
    long long min_s, max_s, min_d, max_d; 
};

// Comparator for std::nth_element based on S coordinate (s = x+y) using indices
struct CompareSByIdx {
    const std::vector<Point>& points_ref;
    CompareSByIdx(const std::vector<Point>& points) : points_ref(points) {}
    bool operator()(int a_idx, int b_idx) const {
        return points_ref[a_idx].s < points_ref[b_idx].s;
    }
};

// Comparator for std::nth_element based on D coordinate (d = x-y) using indices
struct CompareDByIdx {
    const std::vector<Point>& points_ref;
    CompareDByIdx(const std::vector<Point>& points) : points_ref(points) {}
    bool operator()(int a_idx, int b_idx) const {
        return points_ref[a_idx].d < points_ref[b_idx].d;
    }
};

// Recursive function to build the K-D tree
// Operates on a range of indices [begin, end) within the 'point_indices' vector.
// Modifies the range [begin, end) in place using std::nth_element to partition points.
KDNode* build_kdtree_inplace(std::vector<int>::iterator begin, std::vector<int>::iterator end, const std::vector<Point>& all_points, int depth) {
    // Base case: If the range is empty, return nullptr.
    if (begin == end) {
        return nullptr;
    }

    // Determine the splitting axis based on the current depth (alternating S and D)
    int axis = depth % 2; // 0 for S axis, 1 for D axis
    size_t N_local = std::distance(begin, end); // Number of points in the current range
    size_t median_offset = N_local / 2; // Index of the median element within the range
    auto median_it = begin + median_offset; // Iterator to the median element

    // Partition the range of indices around the median point based on the current axis coordinate.
    // std::nth_element rearranges the elements in [begin, end) such that the element at median_it
    // is the element that would be in that position if the range were sorted. All elements before
    // median_it are less than or equal to it, and all elements after are greater than or equal to it.
    if (axis == 0) { // Partition by S coordinate
        std::nth_element(begin, median_it, end, CompareSByIdx(all_points));
    } else { // Partition by D coordinate
        std::nth_element(begin, median_it, end, CompareDByIdx(all_points));
    }

    int median_pt_idx = *median_it; // Get the index (into all_points) of the point at the median position.
    KDNode* node = new KDNode(); // Create a new node for the K-D tree.
    node->point_idx = median_pt_idx; // Store the index of the point in the node.

    // Recursively build the left and right subtrees.
    // The left child will contain points from the range [begin, median_it).
    node->left = build_kdtree_inplace(begin, median_it, all_points, depth + 1);
    // The right child will contain points from the range [median_it + 1, end).
    auto next_it = median_it; 
    ++next_it; // Iterator to the element immediately after the median.
    node->right = build_kdtree_inplace(next_it, end, all_points, depth + 1);

    // Compute the bounding box for the current node's subtree.
    // Start with the coordinates of the point stored in this node.
    const Point& current_point = all_points[node->point_idx];
    node->min_s = node->max_s = current_point.s;
    node->min_d = node->max_d = current_point.d;
    // Expand the bounding box to include the bounding boxes of the children subtrees.
    if (node->left) {
        node->min_s = std::min(node->min_s, node->left->min_s);
        node->max_s = std::max(node->max_s, node->left->max_s);
        node->min_d = std::min(node->min_d, node->left->min_d);
        node->max_d = std::max(node->max_d, node->left->max_d);
    }
    if (node->right) {
        node->min_s = std::min(node->min_s, node->right->min_s);
        node->max_s = std::max(node->max_s, node->right->max_s);
        node->min_d = std::min(node->min_d, node->right->min_d);
        node->max_d = std::max(node->max_d, node->right->max_d);
    }
    
    return node; // Return the newly created node.
}

// Global variable to store the minimum distance found during a single nearest neighbor query.
// Needs to be reset before each query.
long long current_min_dist; 

// Recursive function to find the nearest neighbor for a query point using the K-D tree.
void find_nearest_idx(KDNode* node, const Point& query_point, const std::vector<Point>& all_points, int depth) {
    // Base case: If the node is null, return.
    if (node == nullptr) {
        return; 
    }
    
    // Get the point stored in the current node.
    const Point& current_node_point = all_points[node->point_idx];

    // Calculate the distance from the query point to the point in the current node.
    // Update the global minimum distance if this point is closer and is not the query point itself.
    if (current_node_point.id != query_point.id) { 
        long long dist = chebyshev_dist(current_node_point, query_point);
        current_min_dist = std::min(current_min_dist, dist);
    }

    // Determine the splitting axis based on the current depth.
    int axis = depth % 2;
    KDNode *first_child, *second_child;
    
    // Decide which child subtree to explore first based on the query point's coordinate along the current axis.
    // We prioritize searching the subtree that contains the query point's coordinate.
    long long query_coord = (axis == 0) ? query_point.s : query_point.d;
    long long node_coord = (axis == 0) ? current_node_point.s : current_node_point.d;

    if (query_coord < node_coord) {
        first_child = node->left;
        second_child = node->right;
    } else {
        first_child = node->right;
        second_child = node->left;
    }

    // Recursively search the closer child subtree first.
    find_nearest_idx(first_child, query_point, all_points, depth + 1);

    // Check if the other ("further") subtree could possibly contain a closer point.
    // This is done by calculating the minimum possible distance from the query point to the bounding box of the other subtree.
    long long min_dist_to_bbox = 0;
    if (second_child != nullptr) {
         // Calculate the minimum distance along the S axis from the query point to the second child's bounding box.
         long long ds = 0; 
         if (query_point.s < second_child->min_s) ds = second_child->min_s - query_point.s;
         else if (query_point.s > second_child->max_s) ds = query_point.s - second_child->max_s;
         
         // Calculate the minimum distance along the D axis.
         long long dd = 0; 
         if (query_point.d < second_child->min_d) dd = second_child->min_d - query_point.d;
         else if (query_point.d > second_child->max_d) dd = query_point.d - second_child->max_d;
         
         // The minimum Chebyshev distance to the bounding box is the maximum of the distances along each axis.
         min_dist_to_bbox = std::max(ds, dd); 
    }
   
    // If the minimum distance to the second child's bounding box is less than the current minimum distance found so far,
    // then it's possible that a closer point exists in that subtree, so we need to explore it.
    if (second_child != nullptr && min_dist_to_bbox < current_min_dist) {
        find_nearest_idx(second_child, query_point, all_points, depth + 1);
    }
}

// Recursive function to delete the K-D tree and free allocated memory.
void delete_kdtree(KDNode* node) {
   if (!node) return;
   delete_kdtree(node->left);
   delete_kdtree(node->right);
   delete node;
}

int main() {
    // Use faster I/O operations.
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int N; // Number of points
    std::cin >> N;

    // Vector to store all points.
    std::vector<Point> points(N);
    // Variables to track the minimum and maximum S and D coordinates across all points.
    long long S_min = std::numeric_limits<long long>::max();
    long long S_max = std::numeric_limits<long long>::min();
    long long D_min = std::numeric_limits<long long>::max();
    long long D_max = std::numeric_limits<long long>::min();

    // Read input points, calculate transformed coordinates (s, d), and find the range of s and d values.
    for (int i = 0; i < N; ++i) {
        std::cin >> points[i].x >> points[i].y;
        points[i].s = points[i].x + points[i].y;
        points[i].d = points[i].x - points[i].y;
        points[i].id = i; // Store the original index.

        // Update min/max S and D values.
        S_min = std::min(S_min, points[i].s);
        S_max = std::max(S_max, points[i].s);
        D_min = std::min(D_min, points[i].d);
        D_max = std::max(D_max, points[i].d);
    }
    
    // Create a vector containing indices 0 to N-1. This will be used for building the K-D tree
    // without modifying the original points vector directly.
    std::vector<int> point_indices(N);
    std::iota(point_indices.begin(), point_indices.end(), 0); // Fills the vector with 0, 1, 2, ..., N-1.

    // Build the K-D tree using the indices vector and the points data.
    KDNode* root = build_kdtree_inplace(point_indices.begin(), point_indices.end(), points, 0);

    // Variable to store the minimum P_i value found across all points. Initialize to maximum possible value.
    long long min_P = std::numeric_limits<long long>::max();

    // Iterate through each point i to calculate its P_i value.
    for (int i = 0; i < N; ++i) {
        // Calculate M_i: the maximum Manhattan distance from point i to any other point.
        // This is equivalent to the maximum Chebyshev distance in the transformed (s, d) space.
        // The formula uses the distance from the point to the corners of the bounding box of all points.
        long long max_dist = std::max({points[i].s - S_min, S_max - points[i].s, points[i].d - D_min, D_max - points[i].d});

        // Calculate m_i: the minimum Manhattan distance from point i to any other point j (j != i).
        // Use the K-D tree to find the nearest neighbor in Chebyshev distance.
        current_min_dist = std::numeric_limits<long long>::max(); // Reset the global minimum distance tracker.
        find_nearest_idx(root, points[i], points, 0); // Perform the nearest neighbor search.
        long long min_dist = current_min_dist; // Get the result.
        
        // Calculate P_i = M_i - m_i, the difference between max and min distances.
        long long P_i = max_dist - min_dist;
        // Update the overall minimum P value found so far.
        min_P = std::min(min_P, P_i);
    }

    // Output the final minimum P value.
    std::cout << min_P << std::endl;
    
    // Free the memory allocated for the K-D tree nodes.
    delete_kdtree(root);

    return 0;
}
0