P2894 [USACO08FEB]Hotel G(维护最长连续的1)
题意:
给你长度为n的1串,m次操作
- 找到最左边长度为x连续的1,若找到区间修改为0,找不到,输出0
- 将l-r区间修改为1
思路:
对于1操作,由于左端点不确定,所以我们用线段树维护区间 [ l , r ] [l,r] [l,r]最长的连续的1
即线段树维护
- len区间最长连续空房的长度
- lmx从l端点开始最长连续空房的长度
- rmx从r端点开始最长连续空房的长度
之后在线段树上二分,即可完成1操作。
#include <iostream>
#define lch (k<<1)
#define rch (k<<1|1)
#define mid (l+r>>1)
using namespace std;
const int N=1e5+7;
int n,m;
int len[4*N],sum[4*N],lmx[4*N],rmx[4*N],lz[4*N];
//len区间最长连续空房的长度
//lmx从l端点开始最长连续空房的长度
//rmx从r端点开始最长连续空房的长度
//lazy为1表示退房,为2表示开房
//区间长度,记录后方便计算
void init(int k,int l,int r){
sum[k]=len[k]=lmx[k]=rmx[k]=r-l+1;
lz[k]=-1;
if(l==r){
return;
}
init(lch,l,mid);
init(rch,mid+1,r);
}
void pushup(int k){
lmx[k]=(lmx[lch]==len[lch])? lmx[lch]+lmx[rch]:lmx[lch];
//若左儿子区间全空那么lmax可以横跨左右儿子,否则不能
rmx[k]=(rmx[rch]==len[rch])? rmx[rch]+rmx[lch]:rmx[rch];
//若右儿子区间全空那么rmax可以横跨左右儿子,否则不能
sum[k]=max(rmx[lch]+lmx[rch],max(sum[lch],sum[rch]));
//有三种情况,sum全在左儿子,全在右儿子,横跨左右儿子
}
void pushdown(int k){
if(lz[k]!=-1){
//下传lazy标记
lz[lch]=lz[rch]=lz[k];
sum[lch]=lmx[lch]=rmx[lch]=len[lch]*lz[k];
sum[rch]=lmx[rch]=rmx[rch]=len[rch]*lz[k];
lz[k]=-1;
}
}
//区间赋值0/1
void update(int k,int l,int r,int ql,int qr,int val){
if(ql<=l&&r<=qr){
lmx[k]=rmx[k]=sum[k]=len[k]*val;
lz[k]=val;
return;
}
pushdown(k);
if(ql<=mid) update(lch,l,mid,ql,qr,val);
if(mid+1<=qr) update(rch,mid+1,r,ql,qr,val);
pushup(k);
}
//寻找最左边大于等于x的线段
int query(int k,int l,int r,int x){
if(l==r) return l;
pushdown(k);
if(sum[lch]>=x) return query(lch,l,mid,x);
//递归到左儿子
else if(rmx[lch]+lmx[rch]>=x) return mid-rmx[lch]+1;
//左右儿子合并后满足就用中间
else return query(rch,mid+1,r,x);
//递归到右儿子
}
int ask(int k,int l,int r,int p){
if(l==r) return sum[k];
pushdown(k);
if(p<=mid) return ask(lch,l,mid,p);
else return ask(rch,mid+1,r,p);
}
int main(){
cin>>n>>m;
init(1,1,n);
while(m--){
int op,x,y;
cin>>op;
if(op==1){
cin>>x;
if(sum[1]>=x){
int pos=query(1,1,n,x);
cout<<pos<<"\n";
update(1,1,n,pos,pos+x-1,0);
}else{
cout<<"0\n";
}
}else{
cin>>x>>y;
update(1,1,n,x,x+y-1,1);
}
}
}