对于【usaco月赛】hotel的分析

这道题的题目大意:

有一个区间[1,n],初始都是空的(你可以认为都是0),然后给出n个操作.

操作1格式为1,d,表示表示询问是否有一个r满足[r..r+d-1]都为空,如果有满足条件的,返回最小l,并把[l,l+d-1]全改为1.

操作2格式为2,l,r,表示把区间[l..r]全部赋值为空.

这道题是典型的线段树,我们需要维护几个信息:

1.这个区间的最长连续空区间sum,左端点开始往右的最长连续空区间lsum,以及右端点开始往左的最长连续空区间rsum.

2.区间左右端点l,r.

3.一个tag,等于0表示全部为空,等于1表示全部已填,等于-1表示有空也有填的.

我们就可以写出结构体:

struct tree{
  int l,r;
  int sum,lsum,rsum;
  int tag;
}tr[300001];

结构体写完之后建树,没啥好说的:

void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  tr[k].tag=0;
  tr[k].sum=tr[k].lsum=tr[k].rsum=R-L+1;
  if (L==R) return;
  int mid=(L+R)>>1;
  build(L,mid,k<<1);
  build(mid+1,R,k<<1|1);
}

然后先写好pushup与pushdown.

pushup的操作很简单,就是分三种情况.

1.两个子节点tag都为0,那么此节点tag为0.

2.两个子节点tag都为1,那么此节点tag为1.

3.否则为-1.

那么就可以写了:

inline void pushup(int k){
  int ls=k<<1,rs=k<<1|1;
  if (tr[ls].tag==0&&tr[rs].tag==0) tr[k].tag=0;
  else if (tr[ls].tag==1&&tr[rs].tag==1) tr[k].tag=1;
    else tr[k].tag=-1;
  if (tr[ls].tag==0) tr[k].lsum=tr[ls].sum+tr[rs].lsum;
  else tr[k].lsum=tr[ls].lsum;
  if (tr[rs].tag==0) tr[k].rsum=tr[rs].sum+tr[ls].rsum;
  else tr[k].rsum=tr[rs].rsum;
  tr[k].sum=max(max(tr[ls].sum,tr[rs].sum),tr[ls].rsum+tr[rs].lsum);
}

pushdown也比较简单,也分情况讨论:

1.tag=-1,直接return.

2.tag=0,往下传tag=0.

3.tag=1,往下传tag=1.

别的没什么了:

inline void pushdown(int k){
  if (tr[k].tag==-1) return;
  int ls=k<<1,rs=ls|1;
  if (tr[k].tag==0){
    tr[ls].tag=tr[rs].tag=0;
    tr[ls].sum=tr[ls].lsum=tr[ls].rsum=tr[ls].r-tr[ls].l+1;
    tr[rs].sum=tr[rs].lsum=tr[rs].rsum=tr[rs].r-tr[rs].l+1;
  }else {
    tr[ls].tag=tr[rs].tag=1;
    tr[ls].sum=tr[ls].lsum=tr[ls].rsum=tr[rs].sum=tr[rs].lsum=tr[rs].rsum=0;
  }
}

那么就来看询问.

首先我们判断是否有需要的连续空长度L在线段树中.

直接判断L与整棵树的最长空位长度tr[1].sum的大小即可.

也就是说:

inline bool can(int L){
  if (L<=tr[1].sum) return true;
  else return false;
}

那么我们继续讨论有足够空位的情况.

我们假设我们已经找到了节点k是有足够空位的.

那么我们先看左儿子是否足够,再看中间的是否足够,最后看有儿子是否足够.

值得注意的是,由于中间空位的长度是tr[ls].rsum+tr[rs].lsum,所以若tr[ls].rsum=0,就不用判中间的那段了.

因为若tr[ls].rsum=0,说明中间那段整段都是在右儿子中的.

同理,tr[rs].lsum=0,也不用判了.

若是左边那段,跑左儿子,若是右边那段,跑右儿子.

若已经是叶子节点了,就直接返回tr[k].l或tr[k].r.

然后最重要的就是是中间那段.

这个时候你已经知道最左边的满足的点在左儿子.

所以直接返回tr[ls].r-tr[ls].lsum+1就可以了.

当然有一个没有右儿子的情况,那你就当叶子节点处理,返回tr[k].l或tr[k].r.

但那不存在.

这样就判好了.

其实最后一个判断叶子是一个对L=1的特判.

代码如下(query表示左端点):

int query(int L,int k=1){
  if (tr[k].r==tr[k].l) return tr[k].r;
  pushdown(k);
  int ls=k<<1,rs=k<<1|1;
  if (L<=tr[ls].sum) return query(L,ls);
  if (tr[ls].rsum&&tr[rs].lsum&&L<=tr[ls].rsum+tr[rs].lsum) return tr[ls].r-tr[ls].rsum+1;
  if (L<=tr[rs].sum) return query(L,rs);
}

所以接下来是两个修改.

先讨论改1.

改1的好像很简单,注意pushup与pushdown都写.

那么就是这样:

void change1(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
    tr[k].tag=1;
    tr[k].sum=tr[k].lsum=tr[k].rsum=0;
    return;
  }
  pushdown(k);
  int mid=(tr[k].l+tr[k].r)>>1;
  if (R<=mid) change1(L,R,k<<1);
  else if (L>mid) change1(L,R,k<<1|1);
    else change1(L,mid,k<<1),change1(mid+1,R,k<<1|1);
  pushup(k);
}

所以change0同理:

void change0(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
    tr[k].tag=0;
    tr[k].sum=tr[k].lsum=tr[k].rsum=tr[k].r-tr[k].l+1;
    return;
  }
  pushdown(k);
  int mid=(tr[k].l+tr[k].r)>>1;
  if (R<=mid) change0(L,R,k<<1);
  else if (L>mid) change0(L,R,k<<1|1);
    else change0(L,mid,k<<1),change0(mid+1,R,k<<1|1);
  pushup(k);
}

那么这道题就可以写了.

模拟题目的意思,用到操作的时候用就可以了.

完整代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define rep(i,j,k) for (int i=j;i<=k;i++)
struct tree{
  int l,r;
  int sum,lsum,rsum;
  int tag;
}tr[300001];
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  tr[k].tag=0;
  tr[k].sum=tr[k].lsum=tr[k].rsum=R-L+1;
  if (L==R) return;
  int mid=(L+R)>>1;
  build(L,mid,k<<1);
  build(mid+1,R,k<<1|1);
}
inline void pushup(int k){
  int ls=k<<1,rs=k<<1|1;
  if (tr[ls].tag==0&&tr[rs].tag==0) tr[k].tag=0;
  else if (tr[ls].tag==1&&tr[rs].tag==1) tr[k].tag=1;
    else tr[k].tag=-1;
  if (tr[ls].tag==0) tr[k].lsum=tr[ls].sum+tr[rs].lsum;
  else tr[k].lsum=tr[ls].lsum;
  if (tr[rs].tag==0) tr[k].rsum=tr[rs].sum+tr[ls].rsum;
  else tr[k].rsum=tr[rs].rsum;
  tr[k].sum=max(max(tr[ls].sum,tr[rs].sum),tr[ls].rsum+tr[rs].lsum);
}
inline void pushdown(int k){
  if (tr[k].tag==-1) return;
  int ls=k<<1,rs=ls|1;
  if (tr[k].tag==0){
    tr[ls].tag=tr[rs].tag=0;
    tr[ls].sum=tr[ls].lsum=tr[ls].rsum=tr[ls].r-tr[ls].l+1;
    tr[rs].sum=tr[rs].lsum=tr[rs].rsum=tr[rs].r-tr[rs].l+1;
  }else {
    tr[ls].tag=tr[rs].tag=1;
    tr[ls].sum=tr[ls].lsum=tr[ls].rsum=tr[rs].sum=tr[rs].lsum=tr[rs].rsum=0;
  }
}
inline bool can(int L){
  if (L<=tr[1].sum) return true;
  else return false;
}
int query(int L,int k=1){
  if (tr[k].r==tr[k].l) return tr[k].r;
  pushdown(k);
  int ls=k<<1,rs=k<<1|1;
  if (L<=tr[ls].sum) return query(L,ls);
  if (tr[ls].rsum&&tr[rs].lsum&&L<=tr[ls].rsum+tr[rs].lsum) return tr[ls].r-tr[ls].rsum+1;
  if (L<=tr[rs].sum) return query(L,rs);
}
void change1(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
  	tr[k].tag=1;
  	tr[k].sum=tr[k].lsum=tr[k].rsum=0;
    return;
  }
  pushdown(k);
  int mid=(tr[k].l+tr[k].r)>>1;
  if (R<=mid) change1(L,R,k<<1);
  else if (L>mid) change1(L,R,k<<1|1);
    else change1(L,mid,k<<1),change1(mid+1,R,k<<1|1);
  pushup(k);
}
void change0(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
  	tr[k].tag=0;
  	tr[k].sum=tr[k].lsum=tr[k].rsum=tr[k].r-tr[k].l+1;
    return;
  }
  pushdown(k);
  int mid=(tr[k].l+tr[k].r)>>1;
  if (R<=mid) change0(L,R,k<<1);
  else if (L>mid) change0(L,R,k<<1|1);
    else change0(L,mid,k<<1),change0(mid+1,R,k<<1|1);
  pushup(k);
}
int n,m;
inline void into(){
  int opt,l,r;
  scanf("%d%d",&n,&m);
  build(1,n);
  rep(i,1,m){
    scanf("%d",&opt);
    if (opt==1) {
      scanf("%d",&l);
      if (can(l)) {
        r=query(l);
        printf("%d\n",r);
        change1(r,r+l-1);
      }else printf("0\n");
    }else {
      scanf("%d%d",&l,&r);
      change0(l,l+r-1);
    }
  }
}
int main(){
  into();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/79894063