K-D tree入門【更新ing】
久仰K-D tree大名已久,終於在合適的時候遇見了合適的水題入了坑入了門
K-D tree是什麽
K-D tree是什麽? 按名字上翻譯來就是K維的樹,就是一個用來維護K維空間的點的平衡二叉樹
K-D tree有什麽用
K-D tree可以進行空間上的操作,最經典的就是查詢最近/最遠 點對
還有很多我不知道
K-D tree的原理與實現
K-D tree,又有一個名字叫做劃分樹,與其原理相聯系
類似於普通的平衡樹,對於普通的平衡樹的節點u,其左右子樹分別是權值小於u的和權值大於u的,該節點就相當於在一個值域上在u的值處進行了分割
而kd-tree對於多維空間進行分割,一個節點儲存著以下信息:
struct node{
int d[K],s[2],x[2],y[2],......;
}e[maxn];
d就是該節點儲存的點的K維坐標
s儲存著其左右兒子
剩余的若幹數組儲存著以該節點為根的子樹的各維度的最值
也就是說,一棵子樹實際上對應著一個空間區域,而根節點將該空間區域劃分為左右兩部分
而該空間的信息就儲存在子樹的根節點中
但是這是在一個多維空間,將空間區域劃分有多種方式
一般地,kd-tree垂直於其中一個坐標軸將平面劃分開
以2維為例,如下圖所示
如圖圓圈表示節點,將區域進行劃分
一般遵循以下規律構造:
①各層節點交替劃分各維空間
根節點劃分x坐標
其兒子劃分y坐標
其孫子劃分z坐標
......
②每一層的區域中,按該層劃分的坐標排序,選取其中位數作為劃分點進行劃分
切點作為父節點,左邊的點劃分到左子樹中,右邊的點劃分到右子樹中
③逐層劃分,直至劃分區域無節點
在C++的STL中,有一個函數nth_element()可以在\(O(n)\)時間內將一個數組第k大找出,並將小於的放在左邊,大於的放在右邊
具體實現類似快排
對於K維空間,建樹復雜度\(O(Knlogn)\)
二維建樹代碼如下:
#define ls e[u].s[0]
#define rs e[u].s[1]
#define cmin(x,y) (x > y ? x = y : x)
#define cmax(x,y) (x < y ? x = y : x)
struct point{int d[2];}a[maxn];
struct node{int d[2],s[2],x[2],y[2];}e[maxn];
int n,rt,D,x,y;
bool operator <(const point& a,const point& b){
return a.d[D] == b.d[D] ? a.d[D ^ 1] < b.d[D ^ 1] : a.d[D] < b.d[D];
}
void pup(int u){
if (ls){
cmin(e[u].x[0],e[ls].x[0]); cmax(e[u].x[1],e[ls].x[1]);
cmin(e[u].y[0],e[ls].y[0]); cmax(e[u].y[1],e[ls].y[1]);
}
if (rs){
cmin(e[u].x[0],e[rs].x[0]); cmax(e[u].x[1],e[rs].x[1]);
cmin(e[u].y[0],e[rs].y[0]); cmax(e[u].y[1],e[rs].y[1]);
}
}
int build(int l,int r,int d){
D = d; int u = l + r >> 1;
nth_element(a + l,a + u,a + r + 1);
e[u].d[0] = e[u].x[0] = e[u].x[1] = a[u].d[0];
e[u].d[1] = e[u].y[0] = e[u].y[1] = a[u].d[1];
if (l < u) ls = build(l,u - 1,d ^ 1);
if (r > u) rs = build(u + 1,r,d ^ 1);
pup(u);
return u;
}
查詢最近/遠點對
以最近為例
與普通的暴力不同,在KDtree中查詢最近點對,預期復雜度為\(O(logn)\),可以被卡為\(O(\sqrt{N})\)
我們到一個節點時,用該節點更新答案,並計算左右子樹的估價函數
由於每棵子樹都對應一個區域,可以由此計算出每棵子樹的最近值
如果最近的點的貢獻都比當前答案大,那麽就不用訪問該子樹了
以2維為例,估價函數可以這樣寫:
#define getd(u) (max(x - e[u].x[1],0) + max(e[u].x[0] - x,0) + max(y - e[u].y[1],0) + max(e[u].y[0] - y,0))
#define getdx(u) (max(abs(e[u].x[0] - x),abs(e[u].x[1] - x)) + max(abs(e[u].y[0] - y),abs(e[u].y[1] - y)))
實際上就是求與四個頂點距離的最值
由此可以寫出搜索函數:
void qmx(int u){
LL t = equal(u) ? -INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
if (ls) d[0] = getdx(ls); else d[0] = -INF;
if (rs) d[1] = getdx(rs); else d[1] = -INF;
cmax(mx,t); t = d[0] <= d[1];
if (d[t] > mx) qmx(e[u].s[t]); t ^= 1;
if (d[t] > mx) qmx(e[u].s[t]);
}
void qmn(int u){
int t = equal(u) ? INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
if (ls) d[0] = getd(ls); else d[0] = INF;
if (rs) d[1] = getd(rs); else d[1] = INF;
cmin(mn,t); t = d[0] >= d[1];
if (d[t] < mn) qmn(e[u].s[t]); t ^= 1;
if (d[t] < mn) qmn(e[u].s[t]);
}
例題
由以上基礎,我們就可以輕松A掉SDOI2010 hideseek了
用每個點搜一次就好
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<‘ ‘; puts("");
#define ls e[u].s[0]
#define rs e[u].s[1]
#define cmin(x,y) (x > y ? x = y : x)
#define cmax(x,y) (x < y ? x = y : x)
#define getd(u) (max(x - e[u].x[1],0) + max(e[u].x[0] - x,0) + max(y - e[u].y[1],0) + max(e[u].y[0] - y,0))
#define getdx(u) (max(abs(e[u].x[0] - x),abs(e[u].x[1] - x)) + max(abs(e[u].y[0] - y),abs(e[u].y[1] - y)))
#define equal(u) (e[u].d[0] == x && e[u].d[1] == y)
using namespace std;
const int maxn = 100005,maxm = 100005,INF = 2100000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
struct point{int d[2];}a[maxn];
struct node{int d[2],s[2],x[2],y[2];}e[maxn];
int n,rt,D,x,y; LL mx,mn;
bool operator <(const point& a,const point& b){
return a.d[D] == b.d[D] ? a.d[D ^ 1] < b.d[D ^ 1] : a.d[D] < b.d[D];
}
void pup(int u){
if (ls){
cmin(e[u].x[0],e[ls].x[0]); cmax(e[u].x[1],e[ls].x[1]);
cmin(e[u].y[0],e[ls].y[0]); cmax(e[u].y[1],e[ls].y[1]);
}
if (rs){
cmin(e[u].x[0],e[rs].x[0]); cmax(e[u].x[1],e[rs].x[1]);
cmin(e[u].y[0],e[rs].y[0]); cmax(e[u].y[1],e[rs].y[1]);
}
}
int build(int l,int r,int d){
D = d; int u = l + r >> 1;
nth_element(a + l,a + u,a + r + 1);
e[u].d[0] = e[u].x[0] = e[u].x[1] = a[u].d[0];
e[u].d[1] = e[u].y[0] = e[u].y[1] = a[u].d[1];
if (l < u) ls = build(l,u - 1,d ^ 1);
if (r > u) rs = build(u + 1,r,d ^ 1);
pup(u);
return u;
}
void qmx(int u){
LL t = equal(u) ? -INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
if (ls) d[0] = getdx(ls); else d[0] = -INF;
if (rs) d[1] = getdx(rs); else d[1] = -INF;
cmax(mx,t); t = d[0] <= d[1];
if (d[t] > mx) qmx(e[u].s[t]); t ^= 1;
if (d[t] > mx) qmx(e[u].s[t]);
}
void qmn(int u){
int t = equal(u) ? INF : (abs(e[u].d[0] - x) + abs(e[u].d[1] - y)),d[2];
if (ls) d[0] = getd(ls); else d[0] = INF;
if (rs) d[1] = getd(rs); else d[1] = INF;
cmin(mn,t); t = d[0] >= d[1];
if (d[t] < mn) qmn(e[u].s[t]); t ^= 1;
if (d[t] < mn) qmn(e[u].s[t]);
}
int main(){
n = read();
for (int i = 1; i <= n; i++) a[i].d[0] = read(),a[i].d[1] = read();
rt = build(1,n,0);
LL ans = INF;
for (int i = 1; i <= n; i++){
x = a[i].d[0]; y = a[i].d[1];
mx = 0; qmx(rt);
mn = INF; qmn(rt);
ans = min(ans,mx - mn);
}
printf("%lld\n",ans);
return 0;
}
K-D tree入門【更新ing】