• 【科技】KD-tree随想


    大概就是个复杂度对的暴力做法,在你不想写二维线段树等的时候优秀的替代品。

    优点:思路简单,代码好写。

    他大概有两种用法(虽然差不多)。

    在平面坐标系中干一些事情:

    例如最常规的平面最近最远点,不管是欧几里得距离还是曼哈顿距离,本质上都是一样的。

    利用不同维度的尽量平均的分割,再在询问时剪枝。

    这里给出一个曼哈顿距离上的最近最远距离的版本,可供参考:

     1 namespace KD {
     2     int Rt, lc[N], rc[N], u[N], d[N], l[N], r[N];
     3     inline void Merge(int x, int y) {
     4         u[x] = std::max(u[x], u[y]);
     5         d[x] = std::min(d[x], d[y]);
     6         l[x] = std::min(l[x], l[y]);
     7         r[x] = std::max(r[x], r[y]);
     8     }
     9     inline void Up(int t) {
    10         l[t] = r[t] = p[t].v[0];
    11         u[t] = d[t] = p[t].v[1];
    12         if (lc[t]) Merge(t, lc[t]);
    13         if (rc[t]) Merge(t, rc[t]);
    14     }
    15     int Build(int l, int r, int dep) {
    16         if (l >= r) {
    17             if (l == r) Up(l);
    18             return (l == r)? (l) : (0);
    19         }
    20         Mt = dep & 1; int md = (l + r) >> 1;
    21         std::nth_element(p + l, p + md, p + 1 + r);
    22         lc[md] = Build(l, md - 1, dep + 1);
    23         rc[md] = Build(md + 1, r, dep + 1);
    24         Up(md); return md;
    25     }
    26     inline int In_mi(int t) {
    27         int re = 0;
    28         re += std::max(qi.v[0] - r[t], 0);
    29         re += std::max(l[t] - qi.v[0], 0);
    30         re += std::max(qi.v[1] - u[t], 0);
    31         re += std::max(d[t] - qi.v[1], 0);
    32         return re;
    33     }
    34     inline int In_ma(int t) {
    35         int re = 0;
    36         re += std::max(std::abs(qi.v[0] - r[t]), std::abs(qi.v[0] - l[t]));
    37         re += std::max(std::abs(qi.v[1] - u[t]), std::abs(qi.v[1] - d[t]));
    38         return re;
    39     }
    40     void Query_mi(int t, int dep) {
    41         if (!t) return; Mt = dep & 1;
    42         if (qi != p[t]) ani = std::min(ani, Dis(qi, p[t]));
    43         int dl = (lc[t])? (In_mi(lc[t])) : (INF);
    44         int dr = (rc[t])? (In_mi(rc[t])) : (INF);
    45         if (dl < dr) {
    46             if (ani > dl) Query_mi(lc[t], dep + 1);
    47             if (ani > dr) Query_mi(rc[t], dep + 1);
    48         } else {
    49             if (ani > dr) Query_mi(rc[t], dep + 1);
    50             if (ani > dl) Query_mi(lc[t], dep + 1);
    51         }
    52     }
    53     void Query_ma(int t, int dep) {
    54         if (!t) return; Mt = dep & 1;
    55         ana = std::max(ana, Dis(qi, p[t]));
    56         int dl = (lc[t])? (In_ma(lc[t])) : (0);
    57         int dr = (rc[t])? (In_ma(rc[t])) : (0);
    58         if (dl > dr) {
    59             if (ana < dl) Query_ma(lc[t], dep + 1);
    60             if (ana < dr) Query_ma(rc[t], dep + 1);
    61         } else {
    62             if (ana < dr) Query_ma(rc[t], dep + 1);
    63             if (ana < dl) Query_ma(lc[t], dep + 1);
    64         }
    65     }
    66 }
    View Code

    通常带有表示点的结构体:

    1 struct No {
    2     int v[2];
    3     inline void Read() {
    4         scanf("%d%d", &v[0], &v[1]);
    5     }
    6     inline friend bool operator < (No a, No b) {
    7         return a.v[Mt] < b.v[Mt];
    8     }
    9 } p[N], qi;
    View Code

    (注:$qi$表示当前询问点,$Mt$表示当前分割的维度)

    当然还有某些问题要求第$k$远点,只要每次查到一个点就扔到堆里去,时时维护最远的$k$个就好了,因为KD-tree剪掉了很多不必要的点,所以可以认为扔到堆里的元素并不多。

    或者说动态的问题需要动态开点,开多了就可能导致树不平衡,隔一会重构就好了。

    当然KD-tree在坐标系上最大的优越之处在于乱搞,旋转一下坐标系之后什么都拦不住KD-tree啦。

    比如说APIO 2018的选圈圈。。。把圆用矩形框起来,每次暴力找就好了。

     1 #include <cstdio>
     2 #include <algorithm>
     3 
     4 const int N = 300005;
     5 const double Alpha = 1.926, EPS = 1e-4;
     6 
     7 int n, Mt, ans[N];
     8 
     9 struct No {
    10     double v[2], r; int id;
    11     inline void Read(double x = 0, double y = 0) {
    12         scanf("%lf%lf%lf", &x, &y, &r);
    13         v[0] = x * cos(Alpha) + y * sin(Alpha);
    14         v[1] = y * cos(Alpha) - x * sin(Alpha);
    15     }
    16     inline friend bool operator < (No a, No b) {
    17         return a.v[Mt] < b.v[Mt];
    18     }
    19 } pp[N], p[N], qi;
    20 
    21 inline bool cmp_r(No a, No b) {
    22     return (a.r == b.r)? (a.id < b.id) : (a.r > b.r);
    23 }
    24 inline double Sqr(double x) {
    25     return x * x;
    26 }
    27 
    28 namespace KD {
    29     int Rt, lc[N], rc[N];
    30     double l[N], r[N], d[N], u[N];
    31     inline void Merge(int x, int y) {
    32         l[x] = std::min(l[x], l[y]);
    33         r[x] = std::max(r[x], r[y]);
    34         d[x] = std::min(d[x], d[y]);
    35         u[x] = std::max(u[x], u[y]);
    36     }
    37     inline void Up(int t) {
    38         l[t] = p[t].v[0] - p[t].r;
    39         r[t] = p[t].v[0] + p[t].r;
    40         d[t] = p[t].v[1] - p[t].r;
    41         u[t] = p[t].v[1] + p[t].r;
    42         if (lc[t]) Merge(t, lc[t]);
    43         if (rc[t]) Merge(t, rc[t]);
    44     }
    45     int Build(int l, int r, int dep) {
    46         if (l >= r) return (l == r)? (Up(l), l) : (0);
    47         Mt = dep & 1; int md = (l + r) >> 1;
    48         std::nth_element(p + l, p + md, p + 1 + r);
    49         lc[md] = Build(l, md - 1, dep + 1);
    50         rc[md] = Build(md + 1, r, dep + 1);
    51         Up(md); return md;
    52     }
    53     inline int Out(int t) {
    54         int re1 = r[t] < qi.v[0] - qi.r - EPS || l[t] > qi.v[0] + qi.r + EPS;
    55         int re2 = u[t] < qi.v[1] - qi.r - EPS || d[t] > qi.v[1] + qi.r + EPS;
    56         return re1 || re2;
    57     }
    58     inline int Check(int t) {
    59         return Sqr(p[t].r + qi.r) + EPS >= Sqr(p[t].v[0] - qi.v[0]) + Sqr(p[t].v[1] - qi.v[1]);
    60     }
    61     void Query(int t) {
    62         if (!t || Out(t)) return;
    63         if (!ans[p[t].id] && Check(t)) ans[p[t].id] = qi.id;
    64         if (lc[t]) Query(lc[t]);
    65         if (rc[t]) Query(rc[t]);
    66     }
    67 }
    68 
    69 int main() {
    70     scanf("%d", &n);
    71     for (int i = 1; i <= n; ++i) {
    72         p[i].Read();
    73         p[i].id = i;
    74         pp[i] = p[i];
    75     }
    76     std::sort(pp + 1, pp + 1 + n, cmp_r);
    77     KD::Rt = KD::Build(1, n, 0);
    78     
    79     for (int i = 1; i <= n; ++i) {
    80         if (!ans[pp[i].id]) {
    81             ans[pp[i].id] = pp[i].id;
    82             qi = pp[i];
    83             KD::Query(KD::Rt);
    84         }
    85     }
    86     for (int i = 1; i <= n; ++i) {
    87         printf("%d ", ans[i]);
    88     }
    89     
    90     return 0;
    91 }
    View Code

    二维线段树的替代品:

    由于KD-tree本身就和值域没有什么关系,涉及到二维数点、矩形修改、矩形询问等问题可以比较方便的做,只要每个点维护一个矩形,然后大致就和线段树差不多了。

    其实很多问题都能转化为二维平面甚至多维上的数点问题,有些问题离线后把时间也算成一维也是一个常用套路,KD-tree在这方面处理能力较强,适用范围较广。

    要注意KD-tree上每一个点都是一个真实的点,修改时不要忘记更新它本身。

    可能左右两个子节点表示的矩形存在相交,有时候自顶向下不一定好。

  • 相关阅读:
    AtomicIntegerFieldUpdater 源码分析
    AtomicIntegerArray 源码分析
    AtomicInteger 源码分析
    ArrayBlockingQueue 源码分析
    ReentrantReadWriteLock 源码分析
    ReentrantLock 源码分析
    <Chapter 2>2-2-2.开发Java应用(Developing a Java App)
    <Chapter 2>2-2-2.开发Python应用(Developing a Python App)
    <Chapter 2>2-2-1.用户偏好模式(The User Preferences Pattern)
    <Chapter 2>2-2.开发应用(developing the Application)
  • 原文地址:https://www.cnblogs.com/Dance-Of-Faith/p/9322903.html
Copyright © 2020-2023  润新知