BZOJ - 2716 天使玩偶(CDQ分治)

天涯浪子 提交于 2020-02-06 15:53:56

链接BZOJ - 2716 天使玩偶

题意

在二维坐标系上初始有nn个点(x1,y1),(x2,y2),,(xn,yn)(x_1,y_1),(x_2,y_2),\cdots,(x_n,y_n),共mm个操作,分为以下两种:

  • 1  x  y1\;x\;y:新增点(x,y)(x,y)
  • 2  x  y2\;x\;y:询问离点(x,y)(x,y)曼哈顿距离最近的点,输出该距离(如点A和点B的曼哈顿距离为dist(A,B)=AxBx+AyBydist(A,B)=|A_x-B_x|+|A_y-B_y|

1n,m5×1051\le n,m\le 5\times 10^50x,y1060\le x,y\le 10^6



分析

在三维偏序问题中,CDQ分治可以以O(nlog2n)O(n\log^2n)的时间复杂度解决问题,其过程可以理解为,对第一维有序的序列进行对第二维的归并排序,在归并排序的过程中利用其他数据结构保证第三维(时间复杂度O(logn)O(log n)),并计算左半部分对右半部分的答案贡献

  1. 起初对第一维进行排序,并且计算贡献是计算归并排序的合并过程中左半部分对右半部分的答案贡献,故保证了第一维的偏序关系;
  2. 归并排序过程是以第二维进行排序,即左半部分和右半边部分合并前都是第二维有序的,故保证了第二维的偏序关系;
  3. 第三维的有序由其他数据结构保证,故同样保证了第三维的偏序关系。

考虑将该题转化为三维偏序问题,将操作视为n+mn+m个操作,含新增询问操作,每个操作有一个时间戳tt对于tjt_j时间发生的查询操作(xj,yj)(x_j,y_j)
若仅考虑其左下方的点(xi,yi)(x_i,y_i),故存在偏序关系ti<tj,  xixj,  yiyjt_i\lt t_j,\;x_i\le x_j,\;y_i\le y_j,答案即为ans=min{xjxi+yjyi}ans=min\{x_j-x_i+y_j-y_i\},显然要维护min{xiyi}min\{-x_i-y_i\},故该问题可以利用CDQ分治求解;

同样的,我们可以得到每个区域的偏序关系,答案,以及需维护变量;

处理区域 偏序关系 答案 需维护变量
左下方 ti<tj,  xixj,  yiyjt_i\lt t_j,\;x_i\le x_j,\;y_i\le y_j min{xjxi+yjyi}min\{x_j-x_i+y_j-y_i\} min{xiyi}min\{-x_i-y_i\}
左上方 ti<tj,  xixj,  yiyjt_i\lt t_j,\;x_i\le x_j,\;y_i\ge y_j min{xjxi+yiyj}min\{x_j-x_i+y_i-y_j\} min{xi+yi}min\{-x_i+y_i\}
右下方 ti<tj,  xixj,  yiyjt_i\lt t_j,\;x_i\ge x_j,\;y_i\le y_j min{xixj+yjyi}min\{x_i-x_j+y_j-y_i\} min{xiyi}min\{x_i-y_i\}
右上方 ti<tj,  xixj,  yiyjt_i\lt t_j,\;x_i\ge x_j,\;y_i\ge y_j min{xixj+yiyj}min\{x_i-x_j+y_i-y_j\} min{xi+yi}min\{x_i+y_i\}

正如前面所说,我们先对第一维tt从小到大排序,然后进行CDQ分治,在归并排序的过程中对第二维xx从小到大排序,处理左下方和左上方时从左至右遍历(保证xixjx_i\le x_j),处理右下方和右上方时从右至左遍历(保证xixjx_i\ge x_j),并且计算左半部分对右半部分的贡献(保证ti<tjt_i\lt t_j),那如何保证第三维yy的偏序关系?

我们可以以yiy_i的值建立权值线段树/树状数组,来维护需要维护的变量最小值,当要求yiyjy_i\le y_j时,查找[0,yj][0,y_j],当要求yiyjy_i\ge y_j,查找[yj,106][y_j,10^6]即可。

但是线段树的时间常数较大,会TLE,故需要利用权值树状数组来维护,需要注意树状数组只能维护前缀最值,且不像前缀和一样具有可加性,故需要作些许转换,对于yiyjy_i\le y_j,直接维护并查询yjy_j的前缀最值,但对于yiyjy_i\ge y_j,应当变为106yi106yj10^6-y_i\le 10^6 - y_j,从而只需要查询106yj10^6 - y_j的前缀最值即可。



代码

#include<bits/stdc++.h>
#define lowbit(x) ((x)&(-(x)))
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int maxn=1e6+10;
const int N=1e6;    //坐标范围0~1e6
int n,m;
int ans[maxn];
struct node
{
    int t,x,y;
    int flag;    //0表示增加点,1表示询问点
}a[maxn];
int c[maxn];          //权值树状数组维护前缀最小值
void add(int x,int y)
{
    for(;x<=N;x+=lowbit(x))
        c[x]=min(c[x],y);
}
void clr(int x)       //清除操作
{
    for(;x<=N;x+=lowbit(x))
        c[x]=INF;
}
int ask(int x)
{
    int ans=INF;
    for(;x;x-=lowbit(x))
        ans=min(ans,c[x]);
    return ans;
}
node temp[maxn];
int tot;
void solve(int l,int r)
{
    if(l==r)
        return;
    int mid=(l+r)>>1;
    solve(l,mid);solve(mid+1,r);
 
    int i=l,j;
    for(j=mid+1;j<=r;j++)         //统计左下方贡献,偏序关系为x<=x0,y<=y0
    {                             //ans=min{x0-x+y0-y}=x0+y0+min{-x-y},即维护min{-x-y}
        while(i<=mid&&a[i].x<=a[j].x)
        {
            if(!a[i].flag)
                add(a[i].y,-a[i].x-a[i].y);
            i++;
        }
        if(a[j].flag)
            ans[a[j].t]=min(ans[a[j].t],a[j].x+a[j].y+ask(a[j].y));
    }
    for(int k=l;k<i;k++)      //清空树状数组
    {
        if(!a[k].flag)
            clr(a[k].y);
    }
 
    i=l;
    for(j=mid+1;j<=r;j++)         //统计左上方贡献,偏序关系为x<=x0,y>=y0(即有N-y<=N-y0,由此转化为维护前缀最值)
    {                             //ans=min{x0-x+y-y0}=x0-y0+min{-x+y},即维护min{-x+y}
        while(i<=mid&&a[i].x<=a[j].x)
        {
            if(!a[i].flag)
                add(N-a[i].y,-a[i].x+a[i].y);
            i++;
        }
        if(a[j].flag)
            ans[a[j].t]=min(ans[a[j].t],a[j].x-a[j].y+ask(N-a[j].y));
    }
    for(int k=l;k<i;k++)      //清空树状数组
    {
        if(!a[k].flag)
            clr(N-a[k].y);
    }
 
    i=mid;
    for(j=r;j>=mid+1;j--)         //统计右下方贡献,偏序关系为x>=x0,y<=y0
    {                             //ans=min{x-x0+y0-y}=-x0+y0+min{x-y},即维护min{x-y}
        while(i>=l&&a[i].x>=a[j].x)
        {
            if(!a[i].flag)
                add(a[i].y,a[i].x-a[i].y);
            i--;
        }
        if(a[j].flag)
            ans[a[j].t]=min(ans[a[j].t],-a[j].x+a[j].y+ask(a[j].y));
    }
    for(int k=mid;k>i;k--)      //清空树状数组
    {
        if(!a[k].flag)
            clr(a[k].y);
    }
 
    i=mid;
    for(j=r;j>=mid+1;j--)         //统计右上方贡献,偏序关系为x>=x0,y>=y0(即有N-y<=N-y0,由此转化为维护前缀最值)
    {                             //ans=min{x-x0+y-y0}=-x0-y0+min{x+y},即维护min{x+y}
        while(i>=l&&a[i].x>=a[j].x)
        {
            if(!a[i].flag)
                add(N-a[i].y,a[i].x+a[i].y);
            i--;
        }
        if(a[j].flag)
            ans[a[j].t]=min(ans[a[j].t],-a[j].x-a[j].y+ask(N-a[j].y));
    }
    for(int k=mid;k>i;k--)      //清空树状数组
    {
        if(!a[k].flag)
            clr(N-a[k].y);
    }
 
    //归并排序
    i=l,j=mid+1,tot=0;
    while(i<=mid&&j<=r)
    {
        if(a[i].x<a[j].x)
            temp[tot++]=a[i++];
        else
            temp[tot++]=a[j++];
    }
    while(i<=mid)
        temp[tot++]=a[i++];
    while(j<=r)
        temp[tot++]=a[j++];
    for(int k=0;k<tot;k++)
        a[l+k]=temp[k];
}
vector<int> v;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&a[i].x,&a[i].y);
        a[i].t=i;
        a[i].flag=0;
    }
    for(int i=1;i<=m;i++)
    {
        int op;
        scanf("%d %d %d",&op,&a[n+i].x,&a[n+i].y);
        a[n+i].t=n+i;
        if(op==1)
            a[n+i].flag=0;
        else
        {
            a[n+i].flag=1;
            v.push_back(n+i);
        }
    }
 
    memset(ans,0x3f,sizeof(ans));
    memset(c,0x3f,sizeof(c));
    solve(1,n+m);
    for(auto t:v)
        printf("%d\n",ans[t]);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!