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

链接:BZOJ - 2716 天使玩偶

题意

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

  • 1    x    y 1\;x\;y 1xy:新增点 ( x , y ) (x,y) (x,y)
  • 2    x    y 2\;x\;y 2xy:询问离点 ( x , y ) (x,y) (x,y)曼哈顿距离最近的点,输出该距离(如点A和点B的曼哈顿距离为 d i s t ( A , B ) = ∣ A x − B x ∣ + ∣ A y − B y ∣ dist(A,B)=|A_x-B_x|+|A_y-B_y| dist(A,B)=AxBx+AyBy

1 ≤ n , m ≤ 5 × 1 0 5 1\le n,m\le 5\times 10^5 1n,m5×105 0 ≤ x , y ≤ 1 0 6 0\le x,y\le 10^6 0x,y106



分析

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

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

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

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

处理区域 偏序关系 答案 需维护变量
左下方 t i < t j ,    x i ≤ x j ,    y i ≤ y j t_i\lt t_j,\;x_i\le x_j,\;y_i\le y_j ti<tj,xixj,yiyj m i n { x j − x i + y j − y i } min\{x_j-x_i+y_j-y_i\} min{xjxi+yjyi} m i n { − x i − y i } min\{-x_i-y_i\} min{xiyi}
左上方 t i < t j ,    x i ≤ x j ,    y i ≥ y j t_i\lt t_j,\;x_i\le x_j,\;y_i\ge y_j ti<tj,xixj,yiyj m i n { x j − x i + y i − y j } min\{x_j-x_i+y_i-y_j\} min{xjxi+yiyj} m i n { − x i + y i } min\{-x_i+y_i\} min{xi+yi}
右下方 t i < t j ,    x i ≥ x j ,    y i ≤ y j t_i\lt t_j,\;x_i\ge x_j,\;y_i\le y_j ti<tj,xixj,yiyj m i n { x i − x j + y j − y i } min\{x_i-x_j+y_j-y_i\} min{xixj+yjyi} m i n { x i − y i } min\{x_i-y_i\} min{xiyi}
右上方 t i < t j ,    x i ≥ x j ,    y i ≥ y j t_i\lt t_j,\;x_i\ge x_j,\;y_i\ge y_j ti<tj,xixj,yiyj m i n { x i − x j + y i − y j } min\{x_i-x_j+y_i-y_j\} min{xixj+yiyj} m i n { x i + y i } min\{x_i+y_i\} min{xi+yi}

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

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

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



代码

#include
#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;
}

你可能感兴趣的:(★水题之路,#,【CDQ分治】)