蓝书(算法竞赛进阶指南)刷题记录——BZOJ2716天使玩偶(离线分治+树状数组)

题目:BZOJ2716.
题目大意:维护一个平面初始有 n n n个点,支持 m m m次操作:
1.格式 1   x   y 1\,x\,y 1xy,表示加入一个点 ( x , y ) (x,y) (x,y).
2.格式 2   x   y 2\,x\,y 2xy,表示查询距离坐标 ( x , y ) (x,y) (x,y)最近的点到坐标 ( x , y ) (x,y) (x,y)的距离.
注:这里的距离是曼哈顿距离,即 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的距离为 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2.
1 ≤ n , m ≤ 3 ∗ 1 0 3 , 0 ≤ x i , y i ≤ 1 0 6 1\leq n,m\leq 3*10^3,0\leq x_i,y_i\leq 10^6 1n,m3103,0xi,yi106.

对于每个询问,我们要回答的是:
min ⁡ { ∣ x − x i ∣ + ∣ y − y i ∣ } \min\{|x-x_i|+|y-y_i|\} min{xxi+yyi}

发现有一个绝对值维护起来非常麻烦,所以先所有满足 x i &lt; x x_i&lt;x xi<x y i &lt; y y_i&lt;y yi<y的.原式变成:
min ⁡ { ( x + y ) − ( x i + y i ) } \min\{(x+y)-(x_i+y_i)\} min{(x+y)(xi+yi)}

我们发现 x + y x+y x+y对于每个询问来说是个定值,所以现在就是要使 x i + y i x_i+y_i xi+yi最小.

先考虑只有询问操作.那么直接把初始的几个点看成修改操作并放在询问前离线下来,按照 x x x排序.

之后我们对 y y y维护一个树状数组,并按排序后的顺序扫描每一个操作.对于一个修改 ( x , y ) (x,y) (x,y)直接在树状数组的第 y y y个位置加入一个数 ( x + y ) (x+y) (x+y);对于一个询问直接查询 [ 0 , y ] [0,y] [0,y]上的最大值即可.

考虑有修改操作怎么办.其实我们会发现如果本来就有修改操作只是所有修改都在查询前,加入修改操作只是修改不一定在查询前了,所以考虑对时间进行离线分治.

设每一次分治时间区间 [ L , R ] [L,R] [L,R]的中点为 m i d mid mid.先只考虑时间在 [ L , m i d ] [L,mid] [L,mid]的修改操作对时间在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R]的询问操作的影响,也就是说把上面只有询问操作的算法搬下来就可以了.

操作完之后我们还需要分治下去,分别处理 [ L , m i d ] [L,mid] [L,mid] [ m i d + 1 , R ] [mid+1,R] [mid+1,R]这两个区间.最后到 L = R L=R L=R的时候会发现什么都不用干,直接退出即可.

容易发现这个分治算法复杂度为 O ( ( n + m ) log ⁡ ( n + m ) log ⁡ max ⁡ { y i } ) O((n+m)\log (n+m)\log \max\{y_i\}) O((n+m)log(n+m)logmax{yi}).

然后考虑如何处理其它三种其它情况,发现其它三种情况可以 x x x轴对称, y y y轴对称和原点中心对称转化为第一种情况,直接搞即可,复杂度不变.

当然为了保证树状数组不会操作到位置 0 0 0,我们可以给所有坐标加 1 1 1.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=1000000,INF=(1<<30)-1;
 
int n,m,mx,c[N+9];
 
void Add(int p,int v){if (!p) return;for (;p<=mx;p+=p&-p) c[p]=max(c[p],v);}
int Query(int p){int res=0;for (;p;p-=p&-p) res=max(res,c[p]);return res;}
void Clear(int p){if (!p) return;for (;p<=mx;p+=p&-p) c[p]=0;}
 
struct question{
  int opt,x,y,id;
  question(int Opt=0,int X=0,int Y=0,int Id=0){opt=Opt;x=X;y=Y;id=Id;}
}q[N*2+9],t[N*2+9];
int cq,ans[N*2+9];
 
bool cmp(const question &a,const question &b){return a.x<b.x||a.x==b.x&&a.id<b.id;}
 
void Divide(question *q,int L,int R){
  if (L>=R) return;
  int mid=L+R>>1,lt=L-1,rt=mid;
  for (int i=L;i<=R;++i)
    q[i].id<=mid?t[++lt]=q[i]:t[++rt]=q[i];
  for (int i=L;i<=R;++i) q[i]=t[i];
  int j=L,tmp;
  for (int i=mid+1;i<=R;++i){
    if (q[i].opt) continue;
    for (;j<=mid&&q[j].x<=q[i].x;++j)
      if (q[j].opt) Add(q[j].y,q[j].x+q[j].y);
    tmp=Query(q[i].y);
    if (tmp) ans[q[i].id]=min(ans[q[i].id],q[i].x+q[i].y-tmp);
  }
  for (int i=L;i<j;++i)
    if (q[i].opt) Clear(q[i].y);
  Divide(q,L,mid);
  Divide(q,mid+1,R);
}
 
void Solve(question *q,int n){
  sort(q+1,q+1+n,cmp);
  Divide(q,1,n);
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  int opt,x,y;
  for (int i=1;i<=n;++i){
    scanf("%d%d",&x,&y);++x;++y;
    ++cq;q[cq]=question(1,x,y,cq);
    mx=max(mx,max(x,y));
  }
  for (int i=1;i<=m;++i){
    scanf("%d%d%d",&opt,&x,&y);++x;++y;
    ++cq;q[cq]=question(opt&1,x,y,cq);
    mx=max(mx,max(x,y));
  }
}
 
Abigail work(){
  for (int i=1;i<=cq;++i) ans[i]=INF;
  ++mx;
  Solve(q,cq);
  for (int i=1;i<=cq;++i) q[i].x=mx-q[i].x;
  Solve(q,cq);
  for (int i=1;i<=cq;++i) q[i].y=mx-q[i].y;
  Solve(q,cq);
  for (int i=1;i<=cq;++i) q[i].x=mx-q[i].x;
  Solve(q,cq);
}
 
Abigail outo(){
  for (int i=1;i<=cq;++i)
    if (ans[i]^INF) printf("%d\n",ans[i]);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值