POJ 3667 Hotel (初遇线段树区间合并)

题意:

有一个线段,从1到n,下面m个操作,操作分两个类型,以1开头的是查询操作,以2开头的是更新操作

1 w 表示在总区间内查询一个长度为w的可用区间并且要最靠左,能找到的话返回这个区间的左端点并占用了这个区间,找不到返回0 

2 a len , 表示从单位a开始,清除一段长度为len的区间(将其变为可用,不被占用),不需要输出。

思路:

这是第一次遇到线段树区间合并的题目,写下感悟,还是对线段的更新和查询工作,但是查询的对象的性质已经不像单点那样,查询的是某个线段的最大可用区间是多少,还要一并查询出最大可用区间最左端的位置,显然不能直接记录最左边的位置,因为父亲和儿子没有递推关系,所以要开其他的线段树记录其他的量,每次查询最起码要在O(1)的时间内算出最大可用区间的最左端位置。

而且也不能光记录最大可用间的长度是多少,因为这样父亲和儿子也构不成递推关系。所以要记录开其他的线段树,这是这类区间合并的共同问题,下面直接说解法方法:

可以开三棵线段树,一个记录此区间的最大可用区间的长度,一个记录此区间从左边开始的可用空间的长度,还有一个记录此区间从右边开始的可用空间的长度。记录这三个量可以互相递推pushup掉三棵线段树上所有的父亲。具体的递推关系可以见代码,但这里必须知道为什么可以递推,因为记录了整个区间从左右端开始延伸的可用空间的长度,递归处理左右儿子,就等价与记录了整个区间上哪些位置可用,哪些位置不可用,就把第一课线段树记录的最大可用空间细节化了,接下来可操作性任意强。


//2720 KB 1063 ms
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define M 50005
#define root 1,n,1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
int mmax[M<<2],lmax[M<<2],rmax[M<<2]; 
//mmax记录整个区间的最大连续空位,lmax记录了这个区间从左边开始的连续空位,rmax记录了这个区间从右边开始的连续空位
int col[M<<2]; //lazy tag
int n,m;
void build(int l,int r,int rt)
{
	col[rt]=-1;
	mmax[rt]=lmax[rt]=rmax[rt]=r-l+1;
	if(l==r){
		return;
	}
	int m=(l+r)>>1;
	build(lson);
	build(rson);
}
void pushup(int rt,int m)
{
	lmax[rt]=lmax[rt<<1];
	rmax[rt]=rmax[rt<<1|1];
	if(lmax[rt]==m-(m>>1)) lmax[rt]+=lmax[rt<<1|1];
	if(rmax[rt]==(m>>1)) rmax[rt]+=rmax[rt<<1];
	mmax[rt]=max(rmax[rt<<1]+lmax[rt<<1|1],max(mmax[rt<<1],mmax[rt<<1|1]) );
	//mmax[rt]可能是横跨了左右儿子,也可能是左右儿子中的mmax
}
void pushdown(int rt,int m)
{
	if(col[rt]==-1) return;
	col[rt<<1]=col[rt<<1|1]=col[rt];
	mmax[rt<<1]=lmax[rt<<1]=rmax[rt<<1] = col[rt]? 0 :m-(m>>1);
	mmax[rt<<1|1] = lmax[rt<<1|1] =rmax[rt<<1|1]=col[rt]? 0:(m>>1);
	col[rt]=-1;
}
int query(int x,int l,int r,int rt)
{
	if(mmax[rt]<x){ //也就是mmax[1]<x只有这一种情况
		return 0;
	}
	if(l==r) return l; //这种情况可能发生x=1的时候
	pushdown(rt,r-l+1);
	int m=(l+r)>>1;
	if(mmax[rt<<1]>=x) return query(x,lson);
	if(rmax[rt<<1]+lmax[rt<<1|1]>=x) return m-rmax[rt<<1]+1; //直接找到答案
	if(mmax[rt<<1|1]>=x) return query(x,rson);

}
void update(int L,int R,int c,int l,int r,int rt)
{
	if(L<=l&&r<=R){
		col[rt]=c;
		mmax[rt]=lmax[rt]=rmax[rt]= c? 0:(r-l+1);
		return;
	}
	pushdown(rt,r-l+1);
	int m=(l+r)>>1;
	if(L<=m) update(L,R,c,lson);
	if(R>m) update(L,R,c,rson);
	pushup(rt,r-l+1);

}
int main()
{
	scanf("%d%d",&n,&m);
	build(root);
	while(m--){
		int op;
		scanf("%d",&op);
		if(op==1){
			int x;
			scanf("%d",&x);
			int ans=query(x,root);
			printf("%d\n",ans);
			if(ans) update(ans,ans+x-1,1,root);
		}
		else {
			int a,b;
			scanf("%d%d",&a,&b);
			update(a,a+b-1,0,root);
		}
	}
	return 0;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值