【算法】线段树(经典线段树上二分)
【题意】n个房间,m个询问,每次订最前的连续x个的空房间,或退订从x开始y个房间,求每次订的最左房间号。
【题解】关键在于找连续x个空房间,经典二分。
线段树标记sum,lsum,rsum,表示最长连续房间,从左开始最长连续房间,从右开始最长连续房间。
对于区间k,如果k.sum<x,则无解。
否则,如果l(k).sum>=x,则在左区间。
否则,如果l(k).rsum+r(k).lsum>=x,则在中间,那么l(k).r-l(k).rsum+1就是答案。
否则,则在右区间。
这样可以准确的定位,也体现了线段树被称之为区间树的特点,可以将询问分成若干个完整的区间,只要维护区间信息即可。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=50010; struct tree{int l,r,lsum,rsum,sum,delta;}t[maxn*4]; int n,m; void build(int k,int l,int r){ t[k].l=l;t[k].r=r;t[k].sum=t[k].lsum=t[k].rsum=r-l+1;t[k].delta=-1; if(l==r)return; int mid=(l+r)>>1; build(k<<1,l,mid);build(k<<1|1,mid+1,r); } void modify(int k,int x){ if(x==1){ t[k].lsum=t[k].rsum=t[k].sum=0; } else{ t[k].lsum=t[k].rsum=t[k].sum=t[k].r-t[k].l+1; } } void update(int k){ t[k].sum=max(t[k<<1].rsum+t[k<<1|1].lsum,max(t[k<<1].sum,t[k<<1|1].sum)); t[k].lsum=t[k<<1].lsum;if(t[k<<1].lsum==t[k<<1].r-t[k<<1].l+1)t[k].lsum+=t[k<<1|1].lsum; t[k].rsum=t[k<<1|1].rsum;if(t[k<<1|1].rsum==t[k<<1|1].r-t[k<<1|1].l+1)t[k].rsum+=t[k<<1].rsum; } void push_down(int k){ if(~t[k].delta){ modify(k<<1,t[k].delta);t[k<<1].delta=t[k].delta; modify(k<<1|1,t[k].delta);t[k<<1|1].delta=t[k].delta;//传标记 t[k].delta=-1; } } int ask(int k,int x){ push_down(k); if(t[k].sum<x)return 0; if(t[k<<1].sum>=x)return ask(k<<1,x); if(t[k<<1].rsum+t[k<<1|1].lsum>=x)return t[k<<1].r-t[k<<1].rsum+1; return ask(k<<1|1,x); } void insert(int k,int l,int r,int x){ if(l<=t[k].l&&t[k].r<=r)t[k].delta=x,modify(k,x);//打标记 else{ push_down(k); int mid=(t[k].l+t[k].r)>>1; if(l<=mid)insert(k<<1,l,r,x); if(r>mid)insert(k<<1|1,l,r,x); update(k); } } int main(){ scanf("%d%d",&n,&m); build(1,1,n); int p,x,y; for(int i=1;i<=m;i++){ scanf("%d",&p); if(p==1){ scanf("%d",&x); printf("%d ",y=ask(1,x)); if(y)insert(1,y,y+x-1,1); } else{ scanf("%d%d",&x,&y); insert(1,x,x+y-1,0); } } return 0; }