K-D Tree
基本思想
放在前面:该数据结构的时间复杂度很玄学,一般用于骗分,当然不排除作为正解的可能性。
下面简称该数据结构为
一般在算法竞赛中,我们需要维护的是
下面简单说一下轮换划分法:
我们假设当前以第
事实上,过程是这样的:
-
以当前的关键字选取中位数,对应点作为该子树的根节点。
-
交替选择不同关键字(就是轮换划分法,上面说过了)。
-
将父亲节点向两个儿子连边。
然后看一下
-
树的每一层都依据同一个关键字进行划分(比较显然,看一下上面的过程)。
-
在
中,一棵子树可以划分出一个矩形。
然后我们就可以看一下题了,因为
简单题
神奇玩意,强制在线卡掉了
我们在后面要用到一个不平衡系数,一般取
插入
首先说一下插入。如果当前插入点比当前点在当前统计方式下更小就插入到左子树,否则插入到右子树。
然后如果发现当前点没有东西,直接插入到这个点上。最后记得上传并且判断是否不平衡,如果不平衡则需要重构。代码:
void ins(int &u,node t,int d){
if(!u){//找到插入点
u=get_new();
tr[u].l=tr[u].r=0;//目前没有子树
tr[u].p=t;
pushup(u);
return;
}
if(t.num[d]<=tr[u].p.num[d])ins(tr[u].l,t,(d+1)%2);
else ins(tr[u].r,t,(d+1)%2);
pushup(u);
check(u,d);//判断是否不平衡
}
判断和重构
然后我们说一下判断和重构。判断很好说,就是如果当前点的一个子树的大小已经很大了,我们就需要重构。
接着说一下怎么重构。我们要先找出按照当前关键字的中间值,这个可以 nth_element
即可。
然后我们就把当前点赋成这个中间值的点,然后递归左右子树。这是非常好理解的,所以放一下代码:
void slap(int u){
if(!u)return;
p[++now]=tr[u].p;//收集子树中的点
can[++cnt]=u;//这个点要被重构,换句话说没用了,放进回收站
slap(tr[u].l);
slap(tr[u].r);
}
int rebuild(int l,int r,int d){
if(l>r)return 0;//没有点了
int mid=l+r>>1;
int u=get_new();//开个点
if(d==0)nth_element(p+l,p+mid,p+r+1,cmpx);
else nth_element(p+l,p+mid,p+r+1,cmpy);//找到中间值
tr[u].p=p[mid];//当前点为中间值
tr[u].l=rebuild(l,mid-1,(d+1)%2);
tr[u].r=rebuild(mid+1,r,(d+1)%2);//递归两边
pushup(u);//然后别忘了上传
return u;
}
void check(int &u,int d){
if(tr[tr[u].l].siz>alp*tr[u].siz||tr[tr[u].r].siz>alp*tr[u].siz){//如果某一棵子树大小过大
now=0;
slap(u);//记录子树里的所有点
u=rebuild(1,tr[u].siz,d);//需要重构
}
}
查询
这里要用一个性质,我们上面提到了在
所以我们用四个值代表这个子树的上下左右边界。显然是很好判断这个子树代表的矩形和查询矩形的关系的。如果被包含或者完全无交我们就可以直接返回了。
然后如果仅仅相交,我们先判断当前点是否被包含,以确定是否加上他。然后递归两个子树重复这一过程。
完全无交和被包含的函数非常好理解,看一看就行了。代码:
bool inrange(int x_1,int y_1,int x_2,int y_2,int X_1,int Y_1,int X_2,int Y_2){
return x_1<=X_1&&x_2>=X_2&&y_1<=Y_1&&y_2>=Y_2;
}
bool outrange(int x_1,int y_1,int x_2,int y_2,int X_1,int Y_1,int X_2,int Y_2){
return x_1>X_2||x_2<X_1||y_1>Y_2||y_2<Y_1;
}
int qry(int u,int x_1,int y_1,int x_2,int y_2){
if(!u)return 0;//没有开的点,不造成贡献并且不能递归,否则死循环
if(inrange(x_1,y_1,x_2,y_2,tr[u].mn[0],tr[u].mn[1],tr[u].mx[0],tr[u].mx[1])){
return tr[u].sum;//包含
}
if(outrange(x_1,y_1,x_2,y_2,tr[u].mn[0],tr[u].mn[1],tr[u].mx[0],tr[u].mx[1])){
return 0;//无交
}
int res=0;
if(inrange(x_1,y_1,x_2,y_2,tr[u].p.num[0],tr[u].p.num[1],tr[u].p.num[0],tr[u].p.num[1])){
res+=tr[u].p.val;//要不要加上当前点
}
res+=qry(tr[u].l,x_1,y_1,x_2,y_2)+qry(tr[u].r,x_1,y_1,x_2,y_2);//递归子树(其实就是小矩形)
return res;
}
代码(完整版)
#include<bits/stdc++.h>
#define int long long
#define N 200005
#define alp 0.75
using namespace std;
struct node{
int num[2],val;//两个坐标和权值
node(){};
node(int x,int y,int v){
num[0]=x;
num[1]=y;
val=v;
}
}p[N];
struct tree{
int l,r,mn[2],mx[2],sum,siz;
node p;
}tr[N];
int n,tot,rt,cnt,now,can[N<<2];
bool cmpx(node a,node b){//轮换划分的按两个坐标排序
return a.num[0]<b.num[0];
}
bool cmpy(node a,node b){
return a.num[1]<b.num[1];
}
int get_new(){
if(cnt)return can[cnt--];
return ++tot;
}
void pushup(int u){
for(int i=0;i<2;i++){
tr[u].mn[i]=tr[u].mx[i]=tr[u].p.num[i];//初始值就是点的坐标
if(tr[u].l){
tr[u].mn[i]=min(tr[u].mn[i],tr[tr[u].l].mn[i]);
tr[u].mx[i]=max(tr[u].mx[i],tr[tr[u].l].mx[i]);
}
if(tr[u].r){
tr[u].mn[i]=min(tr[u].mn[i],tr[tr[u].r].mn[i]);
tr[u].mx[i]=max(tr[u].mx[i],tr[tr[u].r].mx[i]);
}
}
tr[u].sum=tr[tr[u].l].sum+tr[tr[u].r].sum+tr[u].p.val;
tr[u].siz=tr[tr[u].l].siz+tr[tr[u].r].siz+1;//平衡树基本pushup
}
void slap(int u){
if(!u)return;
p[++now]=tr[u].p;//收集子树中的点
can[++cnt]=u;//这个点要被重构,换句话说没用了,放进回收站
slap(tr[u].l);
slap(tr[u].r);
}
int rebuild(int l,int r,int d){
if(l>r)return 0;//没有点了
int mid=l+r>>1;
int u=get_new();//开个点
if(d==0)nth_element(p+l,p+mid,p+r+1,cmpx);
else nth_element(p+l,p+mid,p+r+1,cmpy);//找到中间值
tr[u].p=p[mid];//当前点为中间值
tr[u].l=rebuild(l,mid-1,(d+1)%2);
tr[u].r=rebuild(mid+1,r,(d+1)%2);//递归两边
pushup(u);//然后别忘了上传
return u;
}
void check(int &u,int d){
if(tr[tr[u].l].siz>alp*tr[u].siz||tr[tr[u].r].siz>alp*tr[u].siz){//如果某一棵子树大小过大
now=0;
slap(u);//记录子树里的所有点
u=rebuild(1,tr[u].siz,d);//需要重构
}
}
void ins(int &u,node t,int d){
if(!u){//找到插入点
u=get_new();
tr[u].l=tr[u].r=0;//目前没有子树
tr[u].p=t;
pushup(u);
return;
}
if(t.num[d]<=tr[u].p.num[d])ins(tr[u].l,t,(d+1)%2);
else ins(tr[u].r,t,(d+1)%2);
pushup(u);
check(u,d);//判断是否不平衡
}
bool inrange(int x_1,int y_1,int x_2,int y_2,int X_1,int Y_1,int X_2,int Y_2){
return x_1<=X_1&&x_2>=X_2&&y_1<=Y_1&&y_2>=Y_2;
}
bool outrange(int x_1,int y_1,int x_2,int y_2,int X_1,int Y_1,int X_2,int Y_2){
return x_1>X_2||x_2<X_1||y_1>Y_2||y_2<Y_1;
}
int qry(int u,int x_1,int y_1,int x_2,int y_2){
if(!u)return 0;//没有开的点,不造成贡献并且不能递归,否则死循环
if(inrange(x_1,y_1,x_2,y_2,tr[u].mn[0],tr[u].mn[1],tr[u].mx[0],tr[u].mx[1])){
return tr[u].sum;//包含
}
if(outrange(x_1,y_1,x_2,y_2,tr[u].mn[0],tr[u].mn[1],tr[u].mx[0],tr[u].mx[1])){
return 0;//无交
}
int res=0;
if(inrange(x_1,y_1,x_2,y_2,tr[u].p.num[0],tr[u].p.num[1],tr[u].p.num[0],tr[u].p.num[1])){
res+=tr[u].p.val;//要不要加上当前点
}
res+=qry(tr[u].l,x_1,y_1,x_2,y_2)+qry(tr[u].r,x_1,y_1,x_2,y_2);//递归子树(其实就是小矩形)
return res;
}
signed main(){
cin>>n;
int res=0,op;
while(cin>>op){
if(op==1){
int x,y,v;
cin>>x>>y>>v;
x^=res;y^=res;v^=res;
ins(rt,{x,y,v},0);
}
else if(op==2){
int x_1,y_1,x_2,y_2;
cin>>x_1>>y_1>>x_2>>y_2;
x_1^=res;y_1^=res;
x_2^=res;y_2^=res;
res=qry(rt,x_1,y_1,x_2,y_2);
cout<<res<<'\n';
}
else break;
}
return 0;
}
巧克力王国
还是一道比较板的题。我们考虑把一块巧克力看作平面上的点
然后对于一次询问,我们可以求出
然后我们对于巧克力建树。如果发现对于一个人,这个点的子树中的巧克力取到最小都不满足或者取到最大都满足,就可以直接返回。否则递归处理。方式基本和上一题一样,所以直接看代码:
#include<bits/stdc++.h>
#define int long long
#define N 50005
#define alp 0.75
using namespace std;
struct node{
int num[2],val;
node(){};
node(int x,int y,int v){
num[0]=x;
num[1]=y;
val=v;
}
}p[N];
struct tree{
int l,r,siz;
int mx[2],mn[2],sum;
node p;
}tr[N];
int n,m,cnt,tot,rt,can[N],now;
bool cmpx(node a,node b){
return a.num[0]<b.num[0];
}
bool cmpy(node a,node b){
return a.num[1]<b.num[1];
}
int get_new(){
if(cnt)return can[cnt--];
return ++tot;
}
void pushup(int u){
for(int i=0;i<2;i++){
tr[u].mn[i]=tr[u].mx[i]=tr[u].p.num[i];
if(tr[u].l){
tr[u].mn[i]=min(tr[u].mn[i],tr[tr[u].l].mn[i]);
tr[u].mx[i]=max(tr[u].mx[i],tr[tr[u].l].mx[i]);
}
if(tr[u].r){
tr[u].mn[i]=min(tr[u].mn[i],tr[tr[u].r].mn[i]);
tr[u].mx[i]=max(tr[u].mx[i],tr[tr[u].r].mx[i]);
}
}
tr[u].sum=tr[tr[u].l].sum+tr[tr[u].r].sum+tr[u].p.val;
tr[u].siz=tr[tr[u].l].siz+tr[tr[u].r].siz+1;
}
void slap(int u){
if(!u)return;
p[++now]=tr[u].p;
can[++cnt]=u;
slap(tr[u].l);
slap(tr[u].r);
}
int rebuild(int l,int r,int d){
if(l>r)return 0;
int mid=l+r>>1;
int u=get_new();
if(!d)nth_element(p+l,p+mid,p+r+1,cmpx);
else nth_element(p+l,p+mid,p+r+1,cmpy);
tr[u].p=p[mid];
tr[u].l=rebuild(l,mid-1,(d+1)%2);
tr[u].r=rebuild(mid+1,r,(d+1)%2);
pushup(u);
return u;
}
void check(int &u,int d){
if(tr[tr[u].l].siz>alp*tr[u].siz||tr[tr[u].r].siz>alp*tr[u].siz){
now=0;
slap(u);
u=rebuild(1,tr[u].siz,d);
}
}
int qry(int u,int a,int b,int c){
if(!u)return 0;
int mxx=tr[u].mx[0]*a,mnx=tr[u].mn[0]*a;
int mxy=tr[u].mx[1]*b,mny=tr[u].mn[1]*b;
if(a<0)swap(mxx,mnx);
if(b<0)swap(mxy,mny);
if(mnx+mny>=c)return 0;//这里就是在用最大值和最小值判断
if(mxx+mxy<c)return tr[u].sum;
int res=0;
if(tr[u].p.num[0]*a+tr[u].p.num[1]*b<c)res+=tr[u].p.val;
res+=qry(tr[u].l,a,b,c)+qry(tr[u].r,a,b,c);
return res;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>p[i].num[0]>>p[i].num[1]>>p[i].val;
}
rt=rebuild(1,n,0);
while(m--){
int a,b,c;
cin>>a>>b>>c;
cout<<qry(rt,a,b,c)<<'\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】