1. 程式人生 > >poj 3667 Hotel(線段樹,成段更新,區間合併,Lazy思想)

poj 3667 Hotel(線段樹,成段更新,區間合併,Lazy思想)

題意:

有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;
}