http://poj.org/problem?id=3667
题意:
有n个连续的房间,m个操作,共有两种:
1 num 询问是不是有连续长度为num的空房间,若有,输出连续房间的最左边端点。
2 st num 将 [st,st+num-1]的房间清空。
本来想自己敲出这道题,敲到查询的时候没有思路,最后看解题报告,然后自己敲,一直没找到错误。。重敲一遍终于过了。。这道题是很经典的线段树问题。值得好好思考。
思路:
询问区间中满足条件的连续最长区间通常属于区间合并问题。
节点增加4个域,lx:从区间左边数连续空房间的数目。rx:从区间右边数连续空房间的数目。ax:该区间中连续空房间的总数目
col:记录该区间住人的状态,1表示全住满,0表示全空,-1表示有可以住的房间。
查询是否有连续空房间数目num的时候,先查询左边,当tree[v].lx >= num的时候递归左区间;再查询中间,当tree[v*2].lx +tree[v*2+1].rx >= num直接返回最左边端点;最后查询右边,当tree[v].rx>=num递归右区间。col记录该区间的状态,利用了Lazy思想,即当tree[v].col == 1或tree[v].col == 0 时(房间全住满或全不住的情况),要先把这个状态传递到左右子树,并把这个区间的col置为-1。
更新区间[st,st+num-1],即将该区间全置为空或置为满的时候,要先把区间的状态(全空或全满)传递给左右子树,然后递归,最后再把左右子树的状态更新上来,因为子树状态改变,父亲状态肯定也会改变。这个切记不要遗漏。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 50005; int flag; struct line { int l,r; int lx,rx,ax;//lx:从左边起连续空房间数目,rx:从右边起连续空房间数目,ax:整个区间中连续空房间数目最大值 int col;//col为1表示该区间全满,0表示全空,-1表示有房间可以住 }tree[maxn<<2]; void build(int v, int l, int r) { tree[v].l = l; tree[v].r = r; tree[v].col = 0; //初始化为空 tree[v].lx = tree[v].rx = tree[v].ax = r-l+1; if(l == r) return; int mid = (l+r)>>1; build(v*2,l,mid); build(v*2+1,mid+1,r); } int query(int v, int num) { if(tree[v].lx == num && tree[v].r-tree[v].l+1 == num) return tree[v].l;//如果该区间空房间数目与所需相等,直接返回 if(tree[v].ax >= num)//该区间最大连续空房间数目大于所需,要包括三种情况 { if(tree[v].col != -1)//状态传递给左右子树 { tree[v*2].col = tree[v*2+1].col = tree[v].col; if(tree[v].col == 1)//1为全满 { tree[v*2].lx = tree[v*2].rx = tree[v*2].ax = 0; tree[v*2+1].lx = tree[v*2+1].rx = tree[v*2+1].ax = 0; } else if(tree[v].col == 0)//0为全空 { tree[v*2].lx = tree[v*2].rx = tree[v*2].ax = tree[v*2].r-tree[v*2].l+1; tree[v*2+1].lx = tree[v*2+1].rx = tree[v*2+1].ax = tree[v*2+1].r-tree[v*2+1].l+1; } tree[v].col = -1;//自己区间置为-1 } if(tree[v*2].ax >= num)//左边连续房间数目大于所需,递归左区间 return query(v*2,num); if(tree[v*2].rx+tree[v*2+1].lx >= num)//中间连续房间数目大于所需,返回左边端点 return tree[v*2].r-tree[v*2].rx+1; if(tree[v*2+1].ax >= num)//右边连续空房间数目大于所需,递归右区间 return query(v*2+1,num); } return 0; } void update(int v, int l, int r)//更新[l,r]区间 { if(tree[v].l == l && tree[v].r == r)//恰好是要更新区间,直接修改该区间状态 { if(!flag)//flag为0表示表示全空 tree[v].lx = tree[v].rx = tree[v].ax = r-l+1; else//为1表示全满 tree[v].lx = tree[v].rx = tree[v].ax = 0; tree[v].col = flag; return; } int mid = (tree[v].l+tree[v].r)>>1; if(tree[v].col != -1)//状态传递给左右子树 { tree[v*2].col=tree[v*2+1].col=tree[v].col; if(tree[v].col == 1) { tree[v*2].lx=tree[v*2].rx=tree[v*2].ax=0; tree[v*2+1].lx=tree[v*2+1].rx=tree[v*2+1].ax=0; } else if(tree[v].col == 0) { tree[v*2].lx=tree[v*2].rx=tree[v*2].ax=tree[v*2].r-tree[v*2].l+1; tree[v*2+1].lx=tree[v*2+1].rx=tree[v*2+1].ax=tree[v*2+1].r-tree[v*2+1].l+1; } tree[v].col=-1; } if(r<=mid) update(v*2,l,r); else if(l > mid) update(v*2+1,l,r); else { update(v*2,l,mid); update(v*2+1,mid+1,r); } //更新子区间以后,还要up上来,即更新到父节点,求父节点的lx,rx,ax,col. if(tree[v*2].col==0) tree[v].lx=tree[v*2].ax+tree[v*2+1].lx; else tree[v].lx = tree[v*2].lx; if(tree[v*2+1].col==0) tree[v].rx=tree[v*2+1].ax+tree[v*2].rx; else tree[v].rx=tree[v*2+1].rx; tree[v].ax = max(tree[v*2].ax,tree[v*2+1].ax); tree[v].ax = max(max(tree[v].ax,tree[v*2].rx+tree[v*2+1].lx),max(tree[v].lx,tree[v].rx)); if(tree[v].ax == tree[v].r-tree[v].l+1) tree[v].col=0; else if(tree[v].ax==0) tree[v].col=1; else tree[v].col=-1; } int main() { int n,m,x,num,st; scanf("%d %d",&n,&m); build(1,1,n); while(m--) { scanf("%d",&x); if(x == 1) { scanf("%d",&num); st = query(1,num); printf("%d\n",st); if(st) { flag = 1; update(1,st,st+num-1); } } else { flag = 0; scanf("%d %d",&st,&num); update(1,st,st+num-1); } } return 0; }