bzoj 2648 SJY摆棋子
- 钱限题.题面可以看这里.
- 若对每个点都算一遍距离,显然 (T) 爆,在此思想基础上使用 (kd-tree) 配合估价函数来剪枝效果较好.
- 具体来说,对于 (kd-tree) 上的一个节点,我们知道它这颗子树所管辖的范围,算出我们查询的节点到这个范围内可能的最小距离(下界),若这个距离都比当前记录的答案劣或相等,再搜索这颗子树显然没有意义.
- 这样就完成了最优性剪枝.
- 还有一个剪枝,若左子树的距离下界小于右子树的距离下界,则应先搜索左子树,否则先搜索右子树.
- 感性理解一下,若一个儿子的距离下界小,先搜索它,答案更有可能变得更小,如果小于或等于了另一个儿子的距离下界,就没有必要再搜另一个了
- 对于加点的操作,就和二叉搜索树一样,从根节点往下,通过比较确定往哪边走,走到适当的位置插入即可.迭代代替递归,常数较小,但要注意走的同时用新节点更新当前节点的信息.
- 插入过多时,由于 (kd-tree) 无法进行旋转操作保持平衡,树高可能会变得很大,使其退化成链,有三种方法处理.
- 1.记录一个平衡因子 (alpha) ,像替罪羊树那样,及时拍扁重构.这样写最稳定,时间上(应该是)最优的.
- 2.不及时重构,每加入一定量的节点后对整棵树重构,这个量大概在 (10^4) 级别,可自行调整.这样写不太稳定,但实现非常简单,只多了几行.
- 3.离线处理,将所有加点操作都读进来,一开始就全部建好,给每个点加个标记表示是否已经被插入(可用),真正插入时修改标记.这种写法不太推荐,每种操作都要额外考虑标记问题,比较繁琐,而且在资瓷离线的情况下,使用一些离线算法,编写难度和时间效率都远胜于 (kd-tree) .
(kd-tree) 最近点问题下大概是 (O(玄学)) , 处理矩形问题下最坏是 (O(kn^{1-frac 1 k})) ???不会证明,有 (dalao) 得到了证明或证伪麻烦告知...
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mp make_pair
#define pii pair<int,int>
inline int read()
{
int x=0;
bool pos=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
pos=0;
for(;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
return pos?x:-x;
}
const int MAXN=5e5+10;
int n,m,Dimen;
int rt,K=25000;
struct node{
int v[2];
int mi[2],ma[2];
int ls,rs;
bool operator < (const node &rhs) const
{
return v[Dimen]<rhs.v[Dimen];
}
}Tree[MAXN<<1],A[MAXN<<1];
#define root Tree[o]
#define lson Tree[root.ls]
#define rson Tree[root.rs]
#define inf 1e9
void init()
{
for(int i=0;i<2;++i)
{
Tree[0].mi[i]=inf;
Tree[0].ma[i]=-inf;
}
}
inline void pushup(int o)
{
for(int i=0;i<2;++i)
{
root.mi[i]=min(root.mi[i],min(lson.mi[i],rson.mi[i]));
root.ma[i]=max(root.ma[i],max(lson.ma[i],rson.ma[i]));
}
}
int BuildTree(int l,int r,int dimen)
{
Dimen=dimen;
int mid=(l+r)>>1;
int o=mid;
nth_element(A+l,A+mid,A+r+1);
for(int i=0;i<2;++i)
{
root.v[i]=A[mid].v[i];
root.mi[i]=root.ma[i]=root.v[i];
}
root.ls=root.rs=0;
if(l<=mid-1)
root.ls=BuildTree(l,mid-1,dimen^1);
if(mid+1<=r)
root.rs=BuildTree(mid+1,r,dimen^1);
pushup(o);
return o;
}
node querynode;
int ans;//最近距离
int estimate(int o)//估计o到querynode的距离
{
if(!o)
return inf;
int res=0;
for(int i=0;i<2;++i)
{
if(querynode.v[i]<root.mi[i])
res+=root.mi[i]-querynode.v[i];
else if(querynode.v[i]>root.ma[i])
res+=querynode.v[i]-root.ma[i];
}
return res;
}
void query(int o)
{
int dist=abs(root.v[0]-querynode.v[0])+abs(root.v[1]-querynode.v[1]);//实际距离
ans=min(ans,dist);
int dl=estimate(root.ls),dr=estimate(root.rs);
if(dl<dr)
{
if(dl<ans)
query(root.ls);
if(dr<ans)
query(root.rs);
}
else
{
if(dr<ans)
query(root.rs);
if(dl<ans)
query(root.ls);
}
}
void Insert()//像平衡树一样,比较大小向下走,走到合适的地方接上
{
++n;
for(int i=0;i<2;++i)
{
A[n].v[i]=Tree[n].v[i]=read();
Tree[n].mi[i]=Tree[n].ma[i]=Tree[n].v[i];
}
for(int o=rt,dimen=0;o;dimen^=1)
{
for(int i=0;i<2;++i)
{
root.mi[i]=min(root.mi[i],Tree[n].mi[i]);
root.ma[i]=max(root.ma[i],Tree[n].ma[i]);
}
if(Tree[n].v[dimen]<root.v[dimen])
{
if(root.ls)
o=root.ls;
else
{
root.ls=n;
return;
}
}
else
{
if(root.rs)
o=root.rs;
else
{
root.rs=n;
return;
}
}
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;++i)
for(int j=0;j<2;++j)
A[i].v[j]=read();
init();
rt=BuildTree(1,n,0);
while(m--)
{
int op=read();
if(op==1)
{
Insert();
if(n%K==0)
rt=BuildTree(1,n,0);
}
else
{
querynode.v[0]=read();
querynode.v[1]=read();
ans=inf;
query(rt);
printf("%d
",ans);
}
}
return 0;
}