poj 3667 Hotel(線段樹,成段更新,區間合併,Lazy思想)
阿新 • • 發佈:2019-02-20
題意:
有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; }