引入
合并区间其实就是在线段树–lazy标记的基础上,每个点储存多个区间信息。在修改时就需要合并区间。
例题
分析
住店和退房其实就是修改区间,但怎么查找连续为0的区间呢?这就需要合并区间了。
我们可以用lm[k]表示从左数连续为0的长度,rm[k]表示从右数连续为0的区间长度,m[k]表示区间[l~r]的连续为0的长度。
如下丑图:
那么我们就可以得到m从哪几个区间转移过来了。(详见代码)
但还有一个问题,怎么求出最小的编号呢?
显然,当满足条件的位置是rm[2k]+lm[2k+1]时,最小位置为mid-rm[k*2]+1。
代码
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 50000+5
int n;
int m[4*N],lm[4*N],rm[4*N],lazy[4*N];
void built(int k,int l,int r)
{
lazy[k]=-1;
lm[k]=rm[k]=m[k]=r-l+1;//初始化三个数组
if(l==r) return;
int mid=(l+r)>>1;
built(k*2,l,mid);
built(k*2+1,mid+1,r);
}
void pushdown(int k,int l,int r)
{
if(lazy[k]==-1) return;
int mid=(l+r)>>1;
lm[k*2]=rm[k*2]=m[k*2]=(mid-l+1)*lazy[k];//区间整整体改变,三个数组也一起变
lm[k*2+1]=rm[k*2+1]=m[k*2+1]=(r-mid)*lazy[k];
lazy[k*2]=lazy[k*2+1]=lazy[k];
lazy[k]=-1;
}
int ask(int k,int l,int r,int len)
{
if(l==r)
{
if(len==1) return l;
else return 0;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
if(m[k*2]>=len) return ask(k*2,l,mid,len);//题目要求编号最小,则尽可能向左
if(rm[k*2]+lm[k*2+1]>=len) return mid-rm[k*2]+1;//在中间,可以直接求出位置
return ask(k*2+1,mid+1,r,len);//向右找
}
void update(int k,int l,int r,int x,int y,int v)
{
if(x>r||y<l) return;
if(x<=l&&r<=y)
{
lm[k]=rm[k]=m[k]=(r-l+1)*v;
lazy[k]=v;
return;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
update(k*2,l,mid,x,y,v);
update(k*2+1,mid+1,r,x,y,v);
if(lm[k*2]==mid-l+1) lm[k]=lm[k*2]+lm[k*2+1];//左儿子连上了右儿子
else lm[k]=lm[k*2];
if(rm[k*2+1]==r-mid) rm[k]=rm[k*2]+rm[k*2+1];//右儿子全是1,与左儿子相连
else rm[k]=rm[k*2+1];
int maxn;
maxn=max(m[k*2],m[k*2+1]);//找儿子
maxn=max(maxn,rm[k*2]+lm[k*2+1]);//中间
maxn=max(maxn,max(lm[k],rm[k]));//左右取max
m[k]=maxn;
}
int main()
{
// freopen("「USACO2008FEB」Hotel.in","r",stdin);
// freopen("「USACO2008FEB」Hotel.out","w",stdout);
int q;
scanf("%d%d",&n,&q);
built(1,1,n);
for(int i=1; i<=q; i++)
{
int flag;
scanf("%d",&flag);
if(flag==1)
{
int d;
scanf("%d",&d);
int ans=ask(1,1,n,d);
printf("%d\n",ans);
if(ans) update(1,1,n,ans,ans+d-1,0);//用房间就入住
}
else
{
int x,d;
scanf("%d%d",&x,&d);
update(1,1,n,x,x+d-1,1);//退房
}
}
}