浅谈偏序问题与K-D Tree
zhengrunzhe · · 个人记录
I.偏序问题
我们在我们要处理的信息上,加以若干不等式的限制
1.逆序对
比如我们通常做的逆序对,实质就是二维偏序
对于每一个
那么这个不等式组中有两个维度,一个是
那么这个玩意就叫个二维偏序
处理这个玩意的方法有很多,归并排序这里不讲
有种权值线段数组/线段树/平衡树的方法,当然,选择树状数组最简单
由于树状数组处理前缀和只需要跑一次,而区间和要跑两次,所以我们把这个偏序式子反过来变成
对于第一维
对于第二维
大部分题目的值域都比较大,一般要先离散化(如果写平衡树就不用了)
一般二维偏序问题
因为逆序对这个玩意其中一维是
当其中一维不是下标的时候,每个位置有两个权值
其中
然后通常可能每个位置还会伴随着一个权值
这个权值不参与偏序的限制,只作为答案的贡献
比如求和的就是:
或者可以改成求最大值:
这种玩意如果是静态的,通常可以先按照某一维排序,然后该维按位置扫的时候它就是单调的
比如我
高维偏序
通常三维偏序可以排序降维成二维偏序,然而一般的树状数组/线段树/平衡树是无法直接处理二维偏序问题的,这时候我们就需要高级数据结构了
处理偏序问题的利器:K-D Tree,cdq分治,主席树,树套树,四分树(二维线段树),二/高维bit,甚至你还能莫队+值域分块
其实二维偏序可以想象成一个矩形的限制,所以又称矩形数点,但我喜欢叫二维偏序因为听起来b格比较高,对于
三维偏序就能想象成长方体,四维就想象不过来了…
II.K-D Tree
插入(后面会讲怎么做)
单次查询
例如:当k=2时,即处理平面问题的时候,就是
III. KD树与近邻/远离问题
经典运用:
求一个点到所有已知点中的最短距离
这个距离可以是曼哈顿距离,也可以是欧几里得距离,以平面为例
平面曼哈顿距离:
平面欧氏距离:
以小及大:
k维曼哈顿距离:
k维欧氏距离:
inline const friend int manhattan(const point &a,const point &b)
{
int dis=0;
for (int i=0;i<k;i++)
dis+=abs(a.d[i]-b.d[i]);
return dis;
}
inline const friend int sqr_euclid(const point &a,const point &b)
{
int dis=0;
for (int i=0;i<k;i++)
dis+=sqr(a.d[i]-b.d[i]);//sqr(x)表示x的平方,为了避免开根号的精度误差,先用平方算,输出再开根
return 0;
}
那么我们如何查询一个点q到所有已知点的最小距离呢?以曼哈顿距离为例
最暴力地我们肯定要遍历整棵树一遍遍比较距离,显然一次
我们考虑如何去优化这个暴力,比如我们走到哪一棵子树之后发现这整棵子树都不可能成为答案,我们可以去剪枝,一个子树代表了一个矩形,我们可以设计一个估价函数,类似A*算法,计算一个点到矩形的最近/远距离
如图,需要分类讨论,以矩形的两组对边为平行线组分割成九个区域,看图意会一下,第一行是最小,第二行是最大
综合一下,我们可以设计估价函数
inline const int fmin(const point &x)
{
int f=0;
for (int i=0;i<k;i++)
f+=max(mn.d[i]-x.d[i],0)+max(x.d[i]-mx.d[i],0);
return f;
}
inline const int fmax(const point &x)
{
int f=0;
for (int i=0;i<k;i++)
f+=max(abs(mn.d[i]-x.d[i]),abs(mx.d[i]-x.d[i]));
return f;
}
然后我们在树上搜索的时候,子树的最小估价不比当前搜过的答案小,那整个子树就没有意义了,剪枝掉。左右儿子哪个估价更小就先走哪边
int mn,mx;
inline const void querymin(tree *p,const point &x)
{
mn=min(mn,manhattan(p->range,x));
int f[2]={inf,inf};
if (p->son[0]!=null)f[0]=p->son[0]->fmin(x);
if (p->son[1]!=null)f[1]=p->son[1]->fmin(x);
bool t=f[0]>=f[1];
if (f[t]<mn)querymin(p->son[t],x);t^=1;
if (f[t]<mn)querymin(p->son[t],x);
}
inline const void querymax(tree *p,const point &x)
{
mx=max(mx,manhattan(p->range,x));
int f[2]={-inf,-inf};
if (p->son[0]!=null)f[0]=p->son[0]->fmax(x);
if (p->son[1]!=null)f[1]=p->son[1]->fmax(x);
bool t=f[0]<=f[1];
if (f[t]>mx)querymin(p->son[t],x);t^=1;
if (f[t]>mx)querymin(p->son[t],x);
}
类似地,欧氏距离只需要加个平方就好了
inline const int fmin(const point &x)
{
int f=0;
for (int i=0;i<k;i++)
f+=sqr(max(mn.d[i]-x.d[i],0))+sqr(max(x.d[i]-mx.d[i],0));
return f;
}
inline const int fmax(const point &x)
{
int f=0;
for (int i=0;i<k;i++)
f+=max(sqr(mn.d[i]-x.d[i]),sqr(mx.d[i]-x.d[i]));
return f;
}
IV.动态K-DTree
如果我要在kd树插入一个新点,类似bst,容易形成一条长链导致复杂度退化
而如果我用旋转平衡树的方式旋转kd树,显然不太可行,因为kdt的不同深度的排序规则有所不同
这时候我们就想到了替罪羊树,引入平衡因子
inline const bool unbalanced()
{
return son[0]->size>size*alpha||son[1]->size>size*alpha;
}
每次插入后找到最浅的失衡节点,然后暴力拍扁,也许需要开个垃圾桶回收
point a[N]; //回收的点
tree *recycle[N]; //垃圾桶,装载回收的内存
int top,cnt,flag; //代表垃圾桶大小,回收了多少个点,重构子树根的深度
inline tree *spawn(const point &x)
{
tree *p=top?recycle[--top]:tail++;
p->size=1;
p->mx=p->mn=p->range=x;
p->son[0]=p->son[1]=null;
return p;
}
inline const void travel(tree *p)
{
if (p==null)return;
travel(p->son[0]);
a[++cnt]=p->range;recycle[top++]=p;
travel(p->son[1]);
}
inline tree *build(int l,int r,int d)
{
if (l>r)return null;
int mid=l+r>>1;f=d;
nth_element(a+l,a+mid,a+r+1);
tree *p=spawn(a[mid]);
if (l==r)return p;
p->son[0]=build(l,mid-1,(d+1)%k);
p->son[1]=build(mid+1,r,(d+1)%k);
p->pushup();
return p;
}
inline const void rebuild(tree *&p,int d)
{
cnt=0;
travel(p);
p=build(1,cnt,0);
}
inline tree **insert(tree *&p,const point &x,int d)
{
if (p==null)return p=spawn(x),&null;
f=d;tree **bad=insert(p->son[p->range<x],x,(d+1)%k);
p->pushup();
if (p->unbalanced())bad=&p,flag=d;
return bad;
}
public:
inline const void insert(int x,int y)
{
tree **bad=insert(root,point(x,y),flag=0);
if (*bad==null)return;
rebuild(*bad,flag);
}
V.模板题代码&简单偏序题目分析
推出了偏序式子,就可以用kdt实现
二维偏序出来第一维的限制是[l1,r1],第二维的限制是[l2,r2],就可以在kdt上查左下角(l1,l2)右上角(r1,r2)的矩形了,高维同理
平面最近点对
#include<map>
#include<cmath>
#include<cfloat>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef double dbl;
const int N=2e5+10,K=2;
int n;
bool f;
map<pair<dbl,dbl>,int>m;
dbl ans=DBL_MAX;
inline const dbl sqr(dbl x)
{
return x*x;
}
struct point
{
dbl d[K];
inline const bool operator<(const point &p)const
{
return d[f]<p.d[f];
}
inline const bool operator==(const point &p)const
{
for (int i=0;i<K;i++)
if (p.d[i]!=d[i])
return 0;
return 1;
}
inline const friend dbl sqr_euclid(const point &a,const point &b)
{
dbl dis=0;
for (int i=0;i<K;i++)
dis+=sqr(a.d[i]-b.d[i]);
return dis;
}
}a[N];
template<int K>class KD_Tree
{
private:
struct tree
{
tree *son[2];
point range,mn,mx;
inline const void pushup()
{
for (int i=0;i<K;i++)
mn.d[i]=min(range.d[i],min(son[0]->mn.d[i],son[1]->mn.d[i])),
mx.d[i]=max(range.d[i],max(son[0]->mx.d[i],son[1]->mx.d[i]));
}
inline const dbl fmin(const point &x)
{
dbl f=0.0;
for (int i=0;i<K;i++)
f+=pow(max(mn.d[i]-x.d[i],0.0),2.0)+pow(max(0.0,x.d[i]-mx.d[i]),2.0);
return f;
}
}memory_pool[N],*tail,*null,*root;
inline const void init()
{
tail=memory_pool;
null=tail++;
null->son[0]=null->son[1]=null;
for (int i=0;i<K;i++)
null->mn.d[i]=DBL_MAX,
null->mx.d[i]=DBL_MIN;
root=null;
}
inline tree *spawn(const point &x)
{
tree *p=tail++;
p->range=p->mn=p->mx=x;
p->son[0]=p->son[1]=null;
return p;
}
inline const void querymin(tree *p,const point &x)
{
ans=min(ans,x==p->range?DBL_MAX:sqr_euclid(x,p->range));
dbl d[2]={DBL_MAX,DBL_MAX}; //因为查询的点是树中的点,如果搜到它自己了必将影响答案为0,特判一下
if (p->son[0]!=null)d[0]=p->son[0]->fmin(x);
if (p->son[1]!=null)d[1]=p->son[1]->fmin(x);
bool t=d[0]>=d[1];
if (d[t]<mn)querymin(p->son[t],x);t^=1;
if (d[t]<mn)querymin(p->son[t],x);
}
inline tree *build(int l,int r,bool d)
{
if (l>r)return null;
int mid=l+r>>1;f=d;
nth_element(a+l,a+mid,a+r+1);
tree *p=spawn(a[mid]);
if (l==r)return p;
p->son[0]=build(l,mid-1,(d+1)%K);
p->son[1]=build(mid+1,r,(d+1)%K);
return p->pushup(),p;
}
public:
inline KD_Tree(){init();}
inline const void build()
{
root=build(1,n,0);
}
inline const void query(const point &x)
{
querymin(root,x);
}
};
KD_Tree<K>kdt;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lf%lf",&a[i].d[0],&a[i].d[1]);
if (++m[make_pair(a[i].d[0],a[i].d[1])]>1)return puts("0.0000"),0; //有点重合直接输出0
}
kdt.build();
for (int i=1;i<=n;i++)kdt.query(a[i]);
printf("%.4lf\n",sqrt(ans)); //最后记得把方开回去
return 0;
}
动态插入最近曼哈顿 P4169 [Violet]天使玩偶/SJY摆棋子
#include<cstdio>
#include<algorithm>
using std::nth_element;
template<class type>inline const void read(type &in)
{
in=0;char ch=getchar();bool fh=0;
while (ch<48||ch>57){if (ch=='-')fh=1;ch=getchar();}
while (ch>47&&ch<58)in=(in<<3)+(in<<1)+(ch&15),ch=getchar();
if (fh)in=-in;
}
template<class type>inline const void write(type out)
{
if (out>9)write(out/10);
putchar(out%10+48);
}
template<class type>inline const void writeln(type out)
{
if (out<0)out=-out,putchar('-');
write(out);
putchar('\n');
}
template<class type>inline const type max(const type &a,const type &b)
{
return a>b?a:b;
}
template<class type>inline const type min(const type &a,const type &b)
{
return a<b?a:b;
}
const int N=6e5+10,K=2,inf=2147483647;
int n,m;
bool f;
struct point
{
int d[K];
inline point(int x=0,int y=0){d[0]=x;d[1]=y;}
inline const bool operator<(const point &p)const
{
return d[f]<p.d[f];
}
inline const friend int manhattan(const point &a,const point &b)
{
int dis=0;
for (int i=0;i<K;i++)dis+=abs(a.d[i]-b.d[i]);
return dis;
}
}a[N];
template<int k>class KD_Tree
{
private:
static const double alpha=0.5;
struct tree
{
int size;
tree *son[2];
point range,mn,mx;
inline const void pushup()
{
size=son[0]->size+1+son[1]->size;
for (int i=0;i<k;i++)
mn.d[i]=min(range.d[i],min(son[0]->mn.d[i],son[1]->mn.d[i])),
mx.d[i]=max(range.d[i],max(son[0]->mx.d[i],son[1]->mx.d[i]));
}
inline const int evalue(const point &p)
{
int f=0;
for (int i=0;i<k;i++)f+=max(mn.d[i]-p.d[i],0)+max(p.d[i]-mx.d[i],0);
return f;
}
inline const bool unbalanced()
{
return son[0]->size>size*alpha||son[1]->size>size*alpha;
}
}memory_pool[N],*tail,*null,*recycle[N],*root;
int top,flag,cnt,mn;
inline const void init()
{
top=0;
tail=memory_pool;
null=tail++;
null->son[0]=null->son[1]=null;
for (int i=0;i<k;i++)
null->mn.d[i]=inf,
null->mx.d[i]=-inf;
root=null;
}
inline tree *spawn(const point &x)
{
tree *p=top?recycle[--top]:tail++;
p->son[0]=p->son[1]=null;
p->range=p->mn=p->mx=x;
p->size=1;
return p;
}
inline const void travel(tree *p)
{
if (p==null)return;
travel(p->son[0]);
a[++cnt]=p->range;
recycle[top++]=p;
travel(p->son[1]);
}
inline tree *build(int l,int r,int d)
{
if (l>r)return null;
int mid=l+r>>1;f=d;
nth_element(a+l,a+mid,a+r+1);
tree *p=spawn(a[mid]);
if (l==r)return p;
p->son[0]=build(l,mid-1,(d+1)%k);
p->son[1]=build(mid+1,r,(d+1)%k);
p->pushup();
return p;
}
inline const void rebuild(tree *&p,int d)
{
cnt=0;
travel(p);
p=build(1,cnt,d);
}
inline const void query(tree *p,const point &x)
{
mn=min(mn,manhattan(x,p->range));
int f[2]={inf,inf};
if (p->son[0]!=null)f[0]=p->son[0]->evalue(x);
if (p->son[1]!=null)f[1]=p->son[1]->evalue(x);
bool t=f[0]>=f[1];
if (f[t]<mn)query(p->son[t],x);t^=1;
if (f[t]<mn)query(p->son[t],x);
}
inline tree **insert(tree *&p,const point &x,int d)
{
if (p==null)return p=spawn(x),&null;
tree **bad=insert(p->son[p->range.d[d]<x.d[d]],x,(d+1)%k);
p->pushup();
if (p->unbalanced())bad=&p,flag=d;
return bad;
}
public:
inline const void build()
{
init();
root=build(1,n,0);
}
inline const int query(int x,int y)
{
mn=inf;
query(root,point(x,y));
return mn;
}
inline const void insert(int x,int y)
{
tree **bad=insert(root,point(x,y),flag=0);
if (*bad==null)return;
rebuild(*bad,flag);
}
};
KD_Tree<K>kdt;
int main()
{
read(n);read(m);
for (int i=1;i<=n;i++)
for (int j=0;j<K;j++)
read(a[i].d[j]);
kdt.build();
for (int opt,x,y;m--;)
if (read(opt),read(x),read(y),opt&1)kdt.insert(x,y);
else writeln(kdt.query(x,y));
return 0;
}
P2184 贪婪大陆 题解
TATT
排序降维,将第一维从小到大排序,容易写出dp方程
然后这是一个四维偏序求最大值,从头开始扫,即满足了第一维
#include<cstdio>
#include<algorithm>
#define reg register
const int N=5e4+10,INF=1e9+10;
const short K=4;
template<class type>inline const type max(reg const type &a,reg const type &b)
{
return a>b?a:b;
}
template<class type>inline const type min(reg const type &a,reg const type &b)
{
return a<b?a:b;
}
template<class type>inline const void read(reg type &in)
{
in=0;reg char ch=getchar();reg bool fh=0;
while (ch<48||ch>57){if (ch=='-')fh=1;ch=getchar();}
while (ch>47&&ch<58)in=(in<<3)+(in<<1)+(ch&15),ch=getchar();
if (fh)in=-in;
}
short f;
int n,ans,flag;
struct point
{
int d[K],w;
inline point(int x=0,int y=0,int z=0){d[0]=x;d[1]=y;d[2]=z;}
inline const bool operator<(reg const point &p)const
{
if (d[f]^p.d[f])return d[f]<p.d[f];
for (reg short i=0;i<K-flag;i++)
if (d[i]^p.d[i])
return d[i]<p.d[i];
return 0;
}
}a[N];
template<short K>class kD_Tree
{
private:
const static double alpha=0.75;
struct tree
{
int size,mxw;
tree *son[2];
point range,mn,mx;
inline const void pushup()
{
size=son[0]->size+1+son[1]->size;
mx.w=max(max(son[0]->mx.w,son[1]->mx.w),range.w);
for (reg short i=0;i<K;i++)
mn.d[i]=min(range.d[i],min(son[0]->mn.d[i],son[1]->mn.d[i])),
mx.d[i]=max(range.d[i],max(son[0]->mx.d[i],son[1]->mx.d[i]));
}
inline const bool unbalanced()
{
return son[0]->size>size*alpha||son[1]->size>size*alpha;
}
inline const bool in(reg const point &a,reg const point &b)
{
for (reg short i=0;i<K;i++)
if (a.d[i]>mn.d[i]||b.d[i]<mx.d[i])
return 0;
return 1;
}
inline const bool out(reg const point &a,reg const point &b)
{
for (reg short i=0;i<K;i++)
if (a.d[i]>mx.d[i]||b.d[i]<mn.d[i])
return 1;
return 0;
}
inline const bool at(reg const point &a,reg const point &b)
{
for (reg short i=0;i<K;i++)
if (range.d[i]<a.d[i]||range.d[i]>b.d[i])
return 0;
return 1;
}
}memory_pool[N],*tail,*null,*root,*recycle[N];
int top,flag,rnk,mx;
point b[N];
inline const void init()
{
tail=memory_pool;
null=tail++;
root=null->son[0]=null->son[1]=null;
for (reg short i=0;i<K;i++)null->mn.d[i]=INF,null->mx.d[i]=-INF;
}
inline tree *spawn(reg const point &x)
{
reg tree *p=top?recycle[--top]:tail++;
p->son[0]=p->son[1]=null;
p->range=p->mn=p->mx=x;
p->size=1;
return p;
}
inline const void travel(reg tree *p)
{
if (p==null)return;
travel(p->son[0]);
b[++rnk]=p->range;
recycle[top++]=p;
travel(p->son[1]);
}
inline tree *build(reg int l,reg int r,reg short d)
{
if (l>r)return null;
reg int mid=l+r>>1;f=d;
std::nth_element(b+l,b+mid,b+r+1);
tree *p=spawn(b[mid]);
if (l==r)return p;
p->son[0]=build(l,mid-1,(d+1)%K);
p->son[1]=build(mid+1,r,(d+1)%K);
return p->pushup(),p;
}
inline const void rebuild(reg tree *&p,reg int d)
{
rnk=0;
travel(p);
p=build(1,rnk,d);
}
inline const void query(reg tree *p,reg const point &a,reg const point &b)
{
if (p==null)return;
if (p->mx.w<=mx)return;
if (p->out(a,b))return;
if (p->in(a,b))return mx=p->mx.w,void();
if (p->at(a,b))mx=max(p->range.w,mx);
query(p->son[0],a,b);query(p->son[1],a,b);
}
inline tree **insert(reg tree *&p,reg const point &x,reg short d)
{
if (p==null)return p=spawn(x),&null;
tree **bad=insert(p->son[p->range.d[d]<x.d[d]],x,(d+1)%K);
p->pushup();
if (p->unbalanced())bad=&p,flag=d;
return bad;
}
public:
inline kD_Tree(){init();}
inline const int query(reg const point &up)
{
mx=0;query(root,point(-INF,-INF,-INF),up);return mx;
}
inline const void insert(reg const point &p)
{
reg tree **bad=insert(root,p,flag=0);
if (*bad==null)return;
rebuild(*bad,flag);
}
};
kD_Tree<K-1>kdt;
int main()
{
read(n);
for (reg int i=1;i<=n;i++)
for (reg short j=0;j<K;j++)
read(a[i].d[j]);
std::sort(a+1,a+n+1);flag=1;
for (reg int i=1;i<=n;i++)
for (reg short j=0;j<K-1;j++)
a[i].d[j]=a[i].d[j+1];
for (reg int i=1;i<=n;i++)
ans=max(ans,a[i].w=kdt.query(a[i])+1),
kdt.insert(a[i]);
printf("%d\n",ans);
return 0;
}
P4357 [CQOI2016]K远点对
用个大根堆存答案,一开始塞2k个0,然后跑个平面最远点对最后输出堆顶即可