P9061 sol

· · 题解

P9061 [Ynoi2002] Optimal Ordered Problem Solver -sol

发现这是一道很复杂的题目,直接优化了一天才过。

首先发现每一次推平操作一定是从 1 \sim x,1 \sim y 的,所以我们画出图像来一定是一个阶梯。

就像这样:

图中的黑色边框就是我们所得到的轮廓线。

现在考虑每一次加入一个操作的时候(图中绿色线表示),我们的改变主要分为两种:

  1. 对轮廓线上面点的改变。
  2. 对其他散点的改变(图中绿色的点)。

现在来考虑如何维护这些变化(下面以往上推平,也就是操作一为例),对于轮廓线上面的点,我们用两棵线段树维护,分别从横纵坐标两个维度分别维护,也就是维护横坐标为 x 的点的个数和纵坐标为 y 的点的个数。

而在修改的时候,我们发现对于横坐标的线段树是没有改变的, 对于纵坐标的线段树,我们需要把一个区间清空和单点修改,这都是很好维护的。

再来想想其他的散点,我们同样用线段树维护,维护每一个横坐标所对应的纵坐标最小值。这样我们只需要查询一个区间内的纵坐标最小值,不断取出这个最小值使得到某一时刻 1 \sim x 区间的纵坐标最小值都比 y 大的时候就停止。还是非常好理解的。

而对于轮廓线的维护,我们直接用类似于颜色段均摊的方法维护就可以了,只用用一个 set 维护每一个凸出来的点 (x,y) 即可(两个 set 更容易理解但是跑不过)。

那么对于查询我们如何完成呢?

发现这是一个二维数点问题,直接做是双 \log 的,一定是过不了的。

那怎么办呢?

首先我们可以分成轮廓线上面和散点两个部分,轮廓线上面是好处理的,而对于散点,如上图画出的,我们可以先预处理出红色区域的点数,用简单的容斥算出绿色区域内的点,个人就得挺好理解的。

于是这道题思维上面就做完了,感觉比较抽象,常数巨大。。。

在这里总结一下实现部分:

  1. 用两棵线段树分别维护轮廓线上面横纵坐标点的数量。
  2. 用一个 set 维护颜色段均摊,也就是轮廓线。
  3. 用一棵线段树维护散点中区间纵坐标最小的点,对于每一个横坐标相同的点,我们用 vecter 维护(因为其他的似乎要超时)。

注意需要预处理出二维数点,set 维护轮廓线和轮廓线数点的细节很多,大家可以在写的时候画图分析和对拍(这个细节真的很烦)。

代码中写的是 zkw 线段树(可能会快一点),写之前做好心理准备啊!

const int N=1e6+5,inf=2e9;
int n,m,ct=0,bit,dep=0,num=0;
struct Point{int x,y;}a[N];
struct node{int op,x,y,X,Y,c;}q[N];
vector<int> G[N];
vector<pii> f[N],g[N];
bool vis[N];

struct BIT{//维护二维数点
  int tr[N];
  inline int lowbit(int i){return i&(-i);}
  inline void upd(int x,int val){for(reg int i=x;i<=n;i+=lowbit(i)) tr[i]+=val;}
  inline int qry(int x){int res=0;for(reg int i=x;i>=1;i-=lowbit(i)) res+=tr[i];return res;}
}sd,ty;

#define mid ((l+r)>>1)
#define lc p<<1
#define rc p<<1|1
#define lson l,mid,lc
#define rson mid+1,r,rc

inline auto max(auto x,auto y){return (x>y)?x:y;}
inline auto min(auto x,auto y){return (x>y)?y:x;}

struct SGT2{//维护轮廓线上点的线段树
  int tr[N<<2],d[N<<2];
  inline void pu(int p){tr[p]=tr[lc]+tr[rc];}
  inline void pd(int p){if(d[p]) tr[lc]=tr[rc]=tr[p],d[p]=0,d[lc]=d[rc]=1;}
  inline void upd(int p,int val){
    p+=bit;for(reg int i=dep;i>0;--i) pd(p>>i);
    tr[p]+=val;for(p>>=1;p;p>>=1) pu(p);
  }
  inline void covx(int p,int val){
    if(p==0) return;
    p+=bit;for(reg int i=dep;i>0;--i) pd(p>>i);
    tr[p]=val;for(p>>=1;p;p>>=1) pu(p);
  }    
  inline void cov(int x,int y,int delta,int cnt){
    if(x>y||(x==0&&y==0)){upd(y+1,cnt);covx(x-1,delta);return ;}
    x=x+bit-1;y=y+bit+1;
    for(reg int i=dep;i>0;--i) pd(x>>i),pd(y>>i);
    tr[x]=delta;tr[y]+=cnt;
    while(x^y^1){
      if(~x&1) tr[x^1]=0,d[x^1]=1;
      if(y&1) tr[y^1]=0,d[y^1]=1;
      x>>=1;y>>=1;pu(x),pu(y); 
    }
    for(x>>=1;x;x>>=1) pu(x);
  }
  inline int qry(int x,int y){
    if(x>y||(x==0&&y==0)) return 0;
    x=x+bit-1,y=y+bit+1;int res=0;
    for(reg int i=dep;i>0;--i) pd(x>>i),pd(y>>i);
    for(;x^y^1;x>>=1,y>>=1){
      if(~x&1) res+=tr[x^1];
      if(y&1) res+=tr[y^1];
    }return res;
  }
}sx,sy;

struct SGT1{//维护散点的线段树
  int id[N<<2],c[N<<2];
  pii tr[N<<2];
  inline void pu(int p){tr[p]=min(tr[lc],tr[rc]);c[p]=c[lc]+c[rc];}
  inline void build(){
    for(reg int p=bit+1;p<=bit+n;++p) id[p]=0,tr[p]=f[p-bit][0],c[p]=(int)f[p-bit].size()-1;
    for(reg int p=bit-1;p;--p) pu(p);
  } 
  inline void del(int p){
    p+=bit;id[p]++;tr[p]=f[p-bit][id[p]];--c[p];
    for(p>>=1;p;p>>=1) pu(p);
  }
  inline pii qry(int x,int y){
    pii res={n+1,n+1};
    for(x=x+bit-1,y=y+bit+1;x^y^1;x>>=1,y>>=1){
      if(~x&1) res=min(res,tr[x^1]);
      if(y&1) res=min(res,tr[y^1]);
    }return res;
  }
  inline int qryc(int x){
    int res=0,l=1,r=x;
    for(l=l+bit-1,r=r+bit+1;l^r^1;l>>=1,r>>=1){
      if(~l&1) res+=c[l^1];
      if(r&1) res+=c[r^1];  
    }return res;
  }
}t;

set<pii> c;

inline void init(){
  n=read();m=read();
  for(int i=1;i<=n;++i) a[i].x=read(),a[i].y=read(),G[a[i].x].pb(a[i].y),f[a[i].x].pb({a[i].y,i}),ty.upd(a[i].y,1);
  for(bit=1;bit<=n+1;bit<<=1) dep++;
  a[0]=(Point){inf,inf};
  for(int i=1;i<=m;++i){
    q[i].op=read(),q[i].x=read(),q[i].y=read(),q[i].X=read(),q[i].Y=read();
    g[q[i].X].pb({q[i].Y,i});
  }
  num=0;
  for(reg int i=n;i>=1;--i){
    sort(f[i].begin(),f[i].end());
    f[i].pb({n+1,0});
    for(auto j:g[i]) q[j.se].c=num-sd.qry(j.fi);
    for(auto j:G[i]) num++,sd.upd(j,1);
  } //二维数点的预处理
  t.build();c.insert({0,0});c.insert({n+1,0});
}

inline int qry(node nw){
  auto it=c.lb({nw.X,n+1});
  if((*it).se>nw.Y) return 0;//在轮廓线内的要先判掉
  return sx.qry(1,nw.X)-sy.qry(nw.Y+1,n)+t.qryc(nw.X)+ty.qry(nw.Y)+nw.c-n+ct;
}

inline void upd1(node nw){//操作1
  int x=nw.x,y=nw.y,id,l;
  auto it=c.lb({x,n+1});
  l=(*it).se;bool vis=false;
  if(l>=y) return;
  if(it!=c.begin()){
    --it;
    while((*it).se<=y){
      if(it==c.begin()){c.erase(it);vis=true;break;}
      else c.erase(it--);
    }  
    if(vis||(!vis&&(*it).fi!=x)) c.insert({x,y});   
  }
  else c.insert({x,y}); 
  int delta=sx.qry(x+1,n)-sy.qry(1,l-1),cnt=sy.qry(l,y-1)-delta;
  if(cnt>0) sy.cov(l+1,y-1,delta,cnt);//建议自己画图分析一下
  num=0;
  while(a[id=t.qry(1,x).se].y<=y){
    t.del(a[id].x);ty.upd(a[id].y,-1);
    sx.upd(a[id].x,1);++ct;++num;
  }
  if(num) sy.upd(y,num);
}

inline void upd2(node nw){操作2
  int x=nw.x,y=nw.y,id,l=0;
  auto it=c.lb({x,n+1});
  if((*it).se>y) return;
  bool vis=false;
  if(it!=c.begin()){
    --it;
    while((*it).se<=y){
      if(it==c.begin()){c.erase(it);vis=true;break;}
      else c.erase(it--);
    }
    if(vis||(!vis&&(*it).fi!=x)) c.insert({x,y}); 
    l=vis?0:(*it).fi;
  }
  else if((*it).se!=y) c.insert({x,y});
  int delta=sy.qry(y+1,n)-sx.qry(1,l-1),cnt=sx.qry(l,x-1)-delta;
  if(cnt>0) sx.cov(l+1,x-1,delta,cnt);
  num=0;
  while(a[id=t.qry(1,x).se].y<=y){
    t.del(a[id].x);ty.upd(a[id].y,-1);
    sy.upd(a[id].y,1);++ct;++num;
  }
  if(num) sx.upd(x,num);
} 

inline void sol(int i){
  (q[i].op==1)?upd1(q[i]):upd2(q[i]);
  print(qry(q[i]));
}

inline void wrk(){
  init();
  for(reg int i=1;i<=m;++i) sol(i);
}

/*2023.11.24-27 H_W_Y P9061 [Ynoi2002] Optimal Ordered Problem Solver SGT*/
int main(){wrk();return 0;}

容易发现我交了整整 65 发才过。