比较显然的线段树套路题。
考虑用线段树维护七个值,区间 \([l,r]\),区间长度 \(len\),区间内最大的连续空位 \(sum\),从左边最大延伸的长度 \(lmax\),从右边最大延伸的长度 \(ramx\),以及懒标记 \(lazytag\) \((\) \(1\)为开房,\(2\)为退房 \()\)。
接下来下传懒标记。若开房,则区间没有剩余房间。若退房,则区间为空。
现在考虑 \(push\_up\),若左区间全为空房,则有转移
\[tree[x].lmax=tree[x \times 2].len+tree[x \times 2+1].lmax \]
否则父亲节点的 \(lmax\) 得到左子节点的 \(lmax\)。
那么转移 \(ramx\) 同理。
然后不难发现父亲节点的最长区间可为左子节点的最长区间,右子节点的最长区间,以及跨越左右的区间。则有转移:
\[tree[x].sum=\max(tree[x\times 2].sum,tree[x\times 2+1].sum, tree[x \times 2].rmax+tree[x \times 2+1].lmax) \]
到此线段树的基本操作已经完成,对于区间修改,我们在线段树上支持开房和退房两种操作。
最后查询在 \([1,n]\) 中找到一个最前的空区间,满足此区间的长度 \(\geq x\),并返回左端点。
注意查询时按照顺序,即左子节点,左右合并,右子节点,找不到返回 \(0\)。
\(\operatorname{Code}:\)
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
struct node {
int l,r,len,lazytag;
int sum,lmax,rmax;
} tree[N<<2]; //线段树 4 倍空间
void build(int x,int l,int r) {
tree[x].lazytag=0; //懒标记清零
tree[x].l=l, tree[x].r=r, tree[x].len=tree[x].r-tree[x].l+1; //区间左右端点以及区间长度
tree[x].sum=tree[x].lmax=tree[x].rmax=tree[x].len; //初始均为空房,所以连续空房长度都整个区间长度
if(l==r) return;
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
void push_down(int x) { //下传懒标记
if(tree[x].lazytag==0) return; //无标记则返回
if(tree[x].lazytag==1) { //如果要新开房
tree[x<<1].sum=tree[x<<1].lmax=tree[x<<1].rmax=0;
tree[x<<1|1].sum=tree[x<<1|1].lmax=tree[x<<1|1].rmax=0; //这一段没有剩余房间
tree[x<<1].lazytag=tree[x<<1|1].lazytag=1; //下传懒标记
} else { //如果要退房
tree[x<<1].sum=tree[x<<1].lmax=tree[x<<1].rmax=tree[x<<1].len;
tree[x<<1|1].sum=tree[x<<1|1].lmax=tree[x<<1|1].rmax=tree[x<<1|1].len; //这一段都是空的
tree[x<<1].lazytag=tree[x<<1|1].lazytag=2; //下传懒标记
}
tree[x].lazytag=0; //懒标记清零
}
void push_up(int x) { //上传更新
if(tree[x<<1].sum == tree[x<<1].len) tree[x].lmax=tree[x<<1].len+tree[x<<1|1].lmax; //左区间为空房
else tree[x].lmax=tree[x<<1].lmax;
if(tree[x<<1|1].sum == tree[x<<1|1].len) tree[x].rmax=tree[x<<1].rmax+tree[x<<1|1].len; //右区间为空房
else tree[x].rmax=tree[x<<1|1].rmax;
tree[x].sum=max( max(tree[x<<1].sum,tree[x<<1|1].sum), tree[x<<1].rmax+tree[x<<1|1].lmax );
}
void change(int x,int l,int r,int tag) {
if(l<=tree[x].l && tree[x].r<=r) { //修改的区间覆盖当前区间
if(tag==1) tree[x].sum=tree[x].lmax=tree[x].rmax=0; //这一段没有剩余房间
else tree[x].sum=tree[x].lmax=tree[x].rmax=tree[x].len; //这一段都为空
tree[x].lazytag=tag; //下传懒标记
return;
}
push_down(x);
int mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid) change(x<<1,l,r,tag);
if(r>mid) change(x<<1|1,l,r,tag);
push_up(x);
}
int ask(int x,int l,int r,int lengh) {
push_down(x);
if(l==r) return l;
int mid=(l+r)>>1;
if(tree[x<<1].sum>=lengh) return ask(x<<1,l,mid,lengh); //左区间里找
if(tree[x<<1].rmax+tree[x<<1|1].lmax>=lengh) return mid-tree[x<<1].rmax+1;
//左右儿子合并后满足就用中间
if(tree[x<<1|1].sum>=lengh) return ask(x<<1|1,mid+1,r,lengh); //右区间里找
return 0; //找不到则返回 0
}
int main() {
int n,m; scanf("%d%d",&n,&m);
build(1,1,n);
while(m--) {
int opt; scanf("%d",&opt);
if(opt==1) {
int x; scanf("%d",&x);
int Left=ask(1,1,n,x);
printf("%d\n",Left);
if(Left>0) change(1,Left,Left+x-1,1);
} else {
int x,y; scanf("%d%d",&x,&y);
change(1,x,x+y-1,2);
}
}
return 0;
}