题解:P14312 【模板】K-D Tree
发现还能写题解来写一篇
K-D Tree 是一种可以高效处理
1. 建树
K-D Tree 一般使用交叉建树和方差建树两种,一般使用交叉建树。交叉建树的建树方式是:
- 选择一个维度;
- 求出这个维度中点的中位数,将其作为目前的根节点;
- 按中位数把点分成两部分,并选择下一个维度,继续递归建树。
以
现在的问题便是如何求中位数。这时我们注意到,在 algorithm 中提供了函数 nth_element(s+l,s+mid,s+r+1,cmp),可以在
int build(int l,int r,int type){
if(l>r)return 0;
int mid=l+r>>1;
nth_element(tem+l,tem+mid,tem+r+1,[&](int a,int b){
return t[a].x[type]<t[b].x[type];
});
int x=tem[mid];
t[x].ls=build(l,mid-1,(type+1)%k);
t[x].rs=build(mid+1,r,(type+1)%k);
pushup(x);
return x;
}
2. 查询
为了查询
对于这三种情况,我们可以写出以下代码:
int query(int p){
if(p==0)return 0;
pushdown(p);
for(int i=0;i<=k-1;i++)if(t[p].mi[i]>R[i]||t[p].ma[i]<L[i])return 0;//没有交叉直接返回
bool flag=true;
for(int i=0;i<=k-1;i++){
if(L[i]>t[p].mi[i]||t[p].ma[i]>R[i]){
flag=false;
break;
}
}
if(flag)return t[p].sum;//被完全覆盖
int res=0;
flag=true;
for(int i=0;i<=k-1;i++){
if(t[p].x[i]>R[i]||t[p].x[i]<L[i]){
flag=false;
break;
}
}
if(flag)res=t[p].v;
return res+query(t[p].ls)+query(t[p].rs);//有交叉继续递归左右子树
}
3. 动态建树
对于需要中途添加或删除点的情况,我们可以使用二进制分组保持树的平衡,并仍然能保证时间复杂度。
二进制分组,就是建立若干棵 K-D Tree,第
对于加入一个点的操作,我们可以把这个点看做一棵大小为
因为个点每参与一次合并,其所在的树大小就会翻倍。因为最多只有
对于本题,因为有区间查询,我们只需要添加一个 lazy 标记即可。
submission
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+10;
struct node{
int x[3],v,sum,ls,rs,siz,mi[3],ma[3],lazy;
}t[maxn];
int n,lst,rt[20],tem[maxn],L[3],R[3],k,cnt,val,m;
void pushdown(int p){
if(t[p].lazy){
if(t[p].ls){
t[t[p].ls].sum+=t[p].lazy*t[t[p].ls].siz;
t[t[p].ls].v+=t[p].lazy;
t[t[p].ls].lazy+=t[p].lazy;
}
if(t[p].rs){
t[t[p].rs].sum+=t[p].lazy*t[t[p].rs].siz;
t[t[p].rs].v+=t[p].lazy;
t[t[p].rs].lazy+=t[p].lazy;
}
t[p].lazy=0;
}
}
void pushup(int p){
t[p].siz=1+t[t[p].ls].siz+t[t[p].rs].siz;
t[p].sum=t[t[p].ls].sum+t[p].v+t[t[p].rs].sum;
for(int i=0;i<=k-1;i++){
t[p].mi[i]=t[p].ma[i]=t[p].x[i];
if(t[p].ls){
t[p].mi[i]=min(t[p].mi[i],t[t[p].ls].mi[i]);
t[p].ma[i]=max(t[p].ma[i],t[t[p].ls].ma[i]);
}
if(t[p].rs){
t[p].mi[i]=min(t[p].mi[i],t[t[p].rs].mi[i]);
t[p].ma[i]=max(t[p].ma[i],t[t[p].rs].ma[i]);
}
}
}
int build(int l,int r,int type){
if(l>r)return 0;
int mid=l+r>>1;
nth_element(tem+l,tem+mid,tem+r+1,[&](int a,int b){
return t[a].x[type]<t[b].x[type];
});
int x=tem[mid];
t[x].ls=build(l,mid-1,(type+1)%k);
t[x].rs=build(mid+1,r,(type+1)%k);
pushup(x);
return x;
}
void append(int &p){
if(p==0)return;
pushdown(p);
tem[++cnt]=p;
append(t[p].ls);
append(t[p].rs);
p=0;
}
void add(int p){
if(p==0)return;
pushdown(p);
for(int i=0;i<=k-1;i++)if(t[p].mi[i]>R[i]||t[p].ma[i]<L[i])return;
bool flag=true;
for(int i=0;i<=k-1;i++){
if(t[p].mi[i]<L[i]||t[p].ma[i]>R[i]){
flag=false;
break;
}
}
if(flag){
t[p].sum+=t[p].siz*val,t[p].v+=val,t[p].lazy+=val;
return;
}
flag=true;
for(int i=0;i<=k-1;i++){
if(t[p].x[i]>R[i]||t[p].x[i]<L[i]){
flag=false;
break;
}
}
if(flag)t[p].v+=val;
add(t[p].ls);
add(t[p].rs);
pushup(p);
}
int query(int p){
if(p==0)return 0;
pushdown(p);
for(int i=0;i<=k-1;i++)if(t[p].mi[i]>R[i]||t[p].ma[i]<L[i])return 0;
bool flag=true;
for(int i=0;i<=k-1;i++){
if(L[i]>t[p].mi[i]||t[p].ma[i]>R[i]){
flag=false;
break;
}
}
if(flag)return t[p].sum;
int res=0;
flag=true;
for(int i=0;i<=k-1;i++){
if(t[p].x[i]>R[i]||t[p].x[i]<L[i]){
flag=false;
break;
}
}
if(flag)res=t[p].v;
return res+query(t[p].ls)+query(t[p].rs);
}
signed main(){
cin>>k>>m;
n=lst=0;
while(m--){
int op;
cin>>op;
if(op==1){
n++;
for(int i=0;i<=k-1;i++){
cin>>t[n].x[i];
t[n].x[i]^=lst;
}
cin>>t[n].v;
t[n].v^=lst;
tem[cnt=1]=n;
for(int i=1;;i++){
if(rt[i]==0){
rt[i]=build(1,cnt,0);
break;
}else append(rt[i]);
}
}else if(op==2){
for(int i=0;i<=k-1;i++){
cin>>L[i];
L[i]^=lst;
}
for(int i=0;i<=k-1;i++){
cin>>R[i];
R[i]^=lst;
}
cin>>val;
val^=lst;
for(int i=1;i<=18;i++)add(rt[i]);
}else if(op==3){
for(int i=0;i<=k-1;i++){
cin>>L[i];
L[i]^=lst;
}
for(int i=0;i<=k-1;i++){
cin>>R[i];
R[i]^=lst;
}
lst=0;
for(int i=1;i<=18;i++)lst+=query(rt[i]);
cout<<lst<<"\n";
}
}
}