【科技】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上每一个点都是一个真实的点,修改时不要忘记更新它本身。

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

posted @ 2018-07-17 13:35  Dance_Of_Faith  阅读(310)  评论(0编辑  收藏  举报