【JZOJ3591】【NOI2014模拟】数据(CDQ分治+线段树)

Problem

  现在,二维平面上有N个点。实现以下三种操作:
  1. 在点集里添加一个点;
  2. 给出一个点,查询它到点集里所有点的曼哈顿距离的最小值;
  3. 给出一个点,查询它到点集里所有点的曼哈顿距离的最大值。

Hint

  1 ≤ N, Q ≤ 100,000,点的坐标是不超过10^8的非负整数。

Solution

  首先考虑如何破掉曼哈顿距离。设当前要查询的点为(x,y),则可分四种情况讨论:
  1.x<=x0&&y<=y0 (x0+y0)-(x+y)
  2.x<=x0&&y>y0 (x0-y0)-(x-y)
  3.x>x0&&y<=y0 -(x0-y0)+(x-y)
  4.x>x0&&y>y0 -(x0+y0)+(x+y)
  设点的坐标和为xpy,坐标差为xmy,于是我们把曼哈顿距离拆成两个点的xpy/xmy的差。
  我们先考虑不添加点时如何求答案。
  鉴于点的坐标比较大,我们先把所有点的y坐标离散化。然后,把点集和询问丢进一个数组里,将其按x坐标为关键字从小到大排序。考虑开四颗线段树,分别维护minxpy、maxxpy、minxmy、maxxmy。首先顺序扫一遍数组,每次碰到一个点集中的点(x,y),我们就把它丢进四颗线段树中y的位置;每次碰到一个询问(x,y),则分类讨论:若是询问最小值,则根据后两种情况(已按x排序,所以保证了x≥x0),其答案与min(xpy-区间[1,y]的maxxpy,xmy-区间[y,n+q]的maxxmy)取min;若是询问最大值,则其答案与max(xpy-区间[1,y]的minxpy,xmy-区间[y,n+q]的minxmy)取max。扫完后清空线段树(注意,切忌直接memset,应要把原来点集中的点逐点清空,不然原本 O(nlog2n) O ( n l o g 2 n ) (n为点数)的清空耗时就会膨胀为整个线段树的大小)。然后逆序扫一遍数组,碰到点集中的点同样丢进线段树中y的位置,碰到询问也同理。这样,我们便可在 O(nlog2n) O ( n l o g 2 n ) 的时间内求出不添加点时的答案。
  那么考虑解决掉添加点带来的影响。
  我们可以对时间CDQ分治。首先我们像打快排一样,设一个l、r,分别表示我们要做的区间的左右边界。设mid为区间中点,则此区间被分为左右两个区间。我们先递归做左右两个区间,然后再扫一遍左区间,记录下左区间所有的加点操作;扫一遍右区间,记录下右区间所有的询问。则将加点视为点集(因为我们记录的加点均在左区间,出现时间肯定比询问小),按上上段所述的方法更新一波右区间询问的答案。这也是CDQ分治与普通分治的差异之处:左区间会影响右区间。这样的话,我们就付出了时间的代价:将时间复杂度乘上了一个log,但却将问题变成了静态的。
  时间复杂度: O(n(log2n)2) O ( n ( l o g 2 n ) 2 )

Code

#include <cstdio>
#include <algorithm>
using namespace std;
#define N 100001
#define M 8*N
#define S 2147483647
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
int i,n,q,x;
struct point
{
    ll x,y,xpy,xmy;
}a[N];
struct operation
{
    ll t,x,y,xpy,xmy,ans;
}b[N];
struct T
{
    ll x,y,num,xpy,xmy;
}c[M];
struct node
{
    ll min,max;
}f[2][M];
bool compare1(T a,T b)
{
    return a.y<b.y;
}
bool compare2(T a,T b)
{
    return a.x<b.x;
}
void up(bool t,int v)
{
    int a=v*2,b=a+1;
    f[t][v].min=min(f[t][v].min,min(f[t][a].min,f[t][b].min));
    f[t][v].max=max(f[t][v].max,max(f[t][a].max,f[t][b].max));
}
void insert(bool t,int v,int l,int r,int k,int p)
{
    if(l==r)
    {
        f[t][v].min=f[t][v].max=p;
        return;
    }
    int mid=(l+r)/2;
    if(k<=mid)
            insert(t,v*2,l,mid,k,p);
    else    insert(t,v*2+1,mid+1,r,k,p);
    up(t,v);
}
void clear(int t,int v,int l,int r,int k)
{
    f[t][v].max=-(f[t][v].min=S);
    if(l==r)return;
    int mid=(l+r)/2;
    if(k<=mid)
            clear(t,v*2,l,mid,k);
    else    clear(t,v*2+1,mid+1,r,k);
}
ll getmin(int t,int v,int l,int r,int x,int y)
{
    if(l==x&&r==y)return f[t][v].min;
    int mid=(l+r)/2;
    ll ans=S;
    if(x<=mid)ans=min(ans,getmin(t,v*2,l,mid,x,min(mid,y)));
    if(y>mid)ans=min(ans,getmin(t,v*2+1,mid+1,r,max(mid+1,x),y));
    return ans;
}
ll getmax(int t,int v,int l,int r,int x,int y)
{
    if(l==x&&r==y)return f[t][v].max;
    int mid=(l+r)/2;
    ll ans=-S;
    if(x<=mid)ans=max(ans,getmax(t,v*2,l,mid,x,min(mid,y)));
    if(y>mid)ans=max(ans,getmax(t,v*2+1,mid+1,r,max(mid+1,x),y));
    return ans;
}
void work(int as,int bs)
{
    sort(c+1,c+as+bs+1,compare2);
    int i,x;
    fo(i,1,as+bs)
    {
        if((x=c[i].num)<=as)
        {
            insert(0,1,1,n+q,c[i].y,c[i].xpy);
            insert(1,1,1,n+q,c[i].y,c[i].xmy);
            continue;
        }
        if(!b[x-=as].t)continue;
        b[x].ans=b[x].t==1?min(b[x].ans,min(b[x].xpy-getmax(0,1,1,n+q,1,b[x].y),
                                            b[x].xmy-getmax(1,1,1,n+q,b[x].y,n+q))):
                           max(b[x].ans,max(b[x].xpy-getmin(0,1,1,n+q,1,b[x].y),
                                            b[x].xmy-getmin(1,1,1,n+q,b[x].y,n+q)));
    }
    fo(i,1,as+bs)
        if(c[i].num<=as)
        {
            clear(0,1,1,n+q,c[i].y);
            clear(1,1,1,n+q,c[i].y);
        }
    fd(i,as+bs,1)
    {
        if((x=c[i].num)<=as)
        {
            insert(0,1,1,n+q,c[i].y,c[i].xpy);
            insert(1,1,1,n+q,c[i].y,c[i].xmy);
            continue;
        }
        if(!b[x-=as].t)continue;
        b[x].ans=b[x].t==1?min(b[x].ans,min(getmin(1,1,1,n+q,1,b[x].y)-b[x].xmy,
                                            getmin(0,1,1,n+q,b[x].y,n+q)-b[x].xpy)):
                           max(b[x].ans,max(getmax(1,1,1,n+q,1,b[x].y)-b[x].xmy,
                                            getmax(0,1,1,n+q,b[x].y,n+q)-b[x].xpy));
    }
    fd(i,as+bs,1)
        if(c[i].num<=as)
        {
            clear(0,1,1,n+q,c[i].y);
            clear(1,1,1,n+q,c[i].y);
        }
}
void cdq(int l,int r)
{
    if(l==r)return;
    int i,mid=(l+r)/2,as=0,bs=0;
    cdq(l,mid);
    cdq(mid+1,r);
    fo(i,l,mid)
        if(!b[i].t)
            c[++as].x=b[i].x,c[as].y=b[i].y,c[as].num=as,c[as].xpy=b[i].xpy,c[as].xmy=b[i].xmy;
    fo(i,mid+1,r)
        if(b[i].t)
            c[(++bs)+as].x=b[i].x,c[as+bs].y=b[i].y,c[as+bs].num=as+i;
    if(as&&bs)work(as,bs);
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n)
    {
        scanf("%d%d",&a[i].x,&a[i].y);
        c[i].xpy=a[i].xpy=a[i].x+a[i].y;
        c[i].xmy=a[i].xmy=a[i].x-a[i].y;
        c[i].x=a[i].x;
        c[i].y=a[i].y;
        c[i].num=i;
    }
    scanf("%d",&q);
    fo(i,1,q)
    {
        scanf("%d%d%d",&b[i].t,&b[i].x,&b[i].y);
        b[i].xpy=b[i].x+b[i].y;
        b[i].xmy=b[i].x-b[i].y;
        b[i].ans=(b[i].t==1?1:-1)*S;
        c[n+i].x=b[i].x;
        c[n+i].y=b[i].y;
        c[n+i].num=n+i;
    }
    sort(c+1,c+n+q+1,compare1);
    fo(i,1,n+q)
        if((x=c[i].num)<=n)
                c[i].y=a[x].y=i;
        else    c[i].y=b[x-n].y=i;
    fo(i,1,M-1)f[0][i].min=f[1][i].min=S,f[0][i].max=f[1][i].max=-S;
    work(n,q);
    cdq(1,q);
    fo(i,1,q)
        if(b[i].t)
            printf("%lld\n",b[i].ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值