bzoj 2648: SJY摆棋子 k-d tree

Description

这天,SJY显得无聊。在家自己玩。在一个棋盘上,有N个黑色棋子。他每次要么放到棋盘上一个黑色棋子,要么放上一个白色棋子,如果是白色棋子,他会找出距离这个白色棋子最近的黑色棋子。此处的距离是 曼哈顿距离 即(|x1-x2|+|y1-y2|) 。现在给出N<=500000个初始棋子。和M<=500000个操作。对于每个白色棋子,输出距离这个白色棋子最近的黑色棋子的距离。同一个格子可能有多个棋子。

Input

第一行两个数 N M
以后M行,每行3个数 t x y
如果t=1 那么放下一个黑色棋子
如果t=2 那么放下一个白色棋子
Output

对于每个T=2 输出一个最小距离

Sample Input

2 3

1 1

2 3

2 1 2

1 3 3

2 4 2

Sample Output

1

2

HINT

kdtree可以过

分析:这是一道k-d tree求最近点对的模版题。详见代码注释。

代码:

/**************************************************************
    Problem: 2648
    User: beginend
    Language: C++
    Result: Accepted
    Time:12388 ms
    Memory:40356 kb
****************************************************************/

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

const int maxn=5e5+7;
const int kd=2;//维数

using namespace std;

struct rec{
    int d[kd];
};//定义一个n维的向量的类型

struct node{
    int lc,rc;//左右儿子
    int l[kd],r[kd];//每一维的左右边界
    int d[kd];//作为根的这个点的n维向量
}t[maxn*2];

rec a[maxn];

int n,m,cnt,root,D,ans;

bool cmp(rec a,rec b)//判断以D维为关键字,D+1维为第二关键字……的cmp
{
    return (a.d[D]<b.d[D]) || (a.d[D]==b.d[D]) && (a.d[D^1]<b.d[D^1]);
}

void updata(int x,int y)//更新数据,有点像平衡树,其实k-d tree的维护有点像平衡树
{
    for (int i=0;i<kd;i++)
    {
        t[x].l[i]=min(t[x].l[i],t[y].l[i]);
        t[x].r[i]=max(t[x].r[i],t[y].r[i]);
    }
}

void build(int &p,int l,int r,int d)//建k-d tree
{
    D=d;
    if (p==0) p=++cnt; //动态开点
    int mid=(l+r)/2;
    nth_element(a+l,a+mid,a+r+1,cmp);//意思是把第mid小的数放在第mid个位置,并且[l,mid-1]都小于mid,[mid+1,r]都大于mid
    //注意是a+r+1,其他三个系数很像sort的格式
    //这步相当于取中造平衡树,和平衡树找中位数很像
    for (int i=0;i<kd;i++)//更新向量
    {
        t[p].d[i]=a[mid].d[i];
        t[p].l[i]=t[p].r[i]=t[p].d[i];
    }
    if (l<mid) build(t[p].lc,l,mid-1,d^1);//换维建树,对于多维应该是(d+1)%kd
    if (mid<r) build(t[p].rc,mid+1,r,d^1);
    if (t[p].lc) updata(p,t[p].lc);
    if (t[p].rc) updata(p,t[p].rc);
}

void insert(int x)//直接插入的平衡树,splay或替罪羊都没有打,偷懒一下
{
    int i=root;
    D=0;
    while (1)
    {
        updata(i,x);
        if (t[x].d[D]<=t[i].d[D])
        {
            if (!t[i].lc)
            {
                t[i].lc=x;
                return;
            }
            i=t[i].lc;
        }
        else
        {
            if (!t[i].rc)
            {
                t[i].rc=x;
                return;
            }
            i=t[i].rc;
        }
        D^=1;
    }
}

int g(int p,rec s)//估价函数,此处的估价为当前点到以t[p]为根的点围成的矩形的距离(本题为曼哈顿距离),如果点在矩形内则为0。
{
    int ret=0;
    for (int i=0;i<kd;i++)
    {
        ret+=max(t[p].l[i]-s.d[i],0);
        ret+=max(s.d[i]-t[p].r[i],0);
    }
    return ret;
}

int dis(rec a,rec b)//a,b的曼哈顿距离
{
    int ret=0;
    for (int i=0;i<kd;i++)
    {
        ret+=abs(a.d[i]-b.d[i]);
    }
    return ret;
}

void ask(int p,rec s)
{
    rec r;
    int dl=0x3f3f3f3f,dr=0x3f3f3f3f;
    r.d[0]=t[p].d[0];
    r.d[1]=t[p].d[1];
    ans=min(ans,dis(s,r));计算与根的距离
    if (t[p].lc) dl=g(t[p].lc,s);//进行估价
    if (t[p].rc) dr=g(t[p].rc,s);
    if (dl<dr)//反正先枚举估价小的,即离边界最近的
    {
        if (dl<ans) ask(t[p].lc,s);
        if (dr<ans) ask(t[p].rc,s);
    }
    else
    {
        if (dr<ans) ask(t[p].rc,s);
        if (dl<ans) ask(t[p].lc,s);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i].d[0],&a[i].d[1]);
    }
    build(root,1,n,0);

    for (int i=1;i<=m;i++)
    {
        int op;
        scanf("%d",&op);
        if (op==1)
        {
            rec s;
            scanf("%d%d",&s.d[0],&s.d[1]);
            cnt++;
            for (int i=0;i<kd;i++)
            {
                t[cnt].d[i]=s.d[i];
                t[cnt].l[i]=t[cnt].r[i]=t[cnt].d[i];
            }
            insert(cnt);
        }
        else
        {
            rec s;
            scanf("%d%d",&s.d[0],&s.d[1]);
            ans=0x3f3f3f3f;
            ask(root,s);
            printf("%d\n",ans);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/79857138