链接: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 ≤ n , m ≤ 5 × 1 0 5 1\le n,m\le 5\times 10^5 1≤n,m≤5×105, 0 ≤ x , y ≤ 1 0 6 0\le x,y\le 10^6 0≤x,y≤106
在三维偏序问题中,CDQ分治可以以 O ( n log 2 n ) O(n\log^2n) O(nlog2n)的时间复杂度解决问题,其过程可以理解为,对第一维有序的序列进行对第二维的归并排序,在归并排序的过程中利用其他数据结构保证第三维(时间复杂度 O ( l o g n ) O(log n) O(logn)),并计算左半部分对右半部分的答案贡献。
考虑将该题转化为三维偏序问题,将操作视为 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,xi≤xj,yi≤yj,答案即为 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{xj−xi+yj−yi},显然要维护 m i n { − x i − y i } min\{-x_i-y_i\} min{−xi−yi},故该问题可以利用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,xi≤xj,yi≤yj | m i n { x j − x i + y j − y i } min\{x_j-x_i+y_j-y_i\} min{xj−xi+yj−yi} | 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\le x_j,\;y_i\ge y_j ti<tj,xi≤xj,yi≥yj | m i n { x j − x i + y i − y j } min\{x_j-x_i+y_i-y_j\} min{xj−xi+yi−yj} | 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,xi≥xj,yi≤yj | m i n { x i − x j + y j − y i } min\{x_i-x_j+y_j-y_i\} min{xi−xj+yj−yi} | 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\ge y_j ti<tj,xi≥xj,yi≥yj | m i n { x i − x j + y i − y j } min\{x_i-x_j+y_i-y_j\} min{xi−xj+yi−yj} | 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 xi≤xj),处理右下方和右上方时从右至左遍历(保证 x i ≥ x j x_i\ge x_j xi≥xj),并且计算左半部分对右半部分的贡献(保证 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 yi≤yj时,查找 [ 0 , y j ] [0,y_j] [0,yj],当要求 y i ≥ y j y_i\ge y_j yi≥yj,查找 [ y j , 1 0 6 ] [y_j,10^6] [yj,106]即可。
但是线段树的时间常数较大,会TLE,故需要利用权值树状数组来维护,需要注意树状数组只能维护前缀最值,且不像前缀和一样具有可加性,故需要作些许转换,对于 y i ≤ y j y_i\le y_j yi≤yj,直接维护并查询 y j y_j yj的前缀最值,但对于 y i ≥ y j y_i\ge y_j yi≥yj,应当变为 1 0 6 − y i ≤ 1 0 6 − y j 10^6-y_i\le 10^6 - y_j 106−yi≤106−yj,从而只需要查询 1 0 6 − y j 10^6 - y_j 106−yj的前缀最值即可。
#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;
}