POJ-3667 Hotel 线段树

本文介绍了一种利用线段树解决区间更新及查询最长合法区间的问题。通过维护每个节点的最长合法区间、左端最长合法区间及右端最长合法区间,实现了高效的区间操作。文中还提供了详细的AC代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:POJ-3667

主要思路:

为了书写方便,记Llen为该区间从其左端点向右开始的最长合法区间,Rlen为从右端点向左开始的最长合法区间.

此题用线段树写应该是很好看出来的(从那么多的区间操作中).维护每个区间的Llen与Rlen,还有一个区间中最长的合法区间(方便1操作判断是否有位置).查找时从最大的区间[1,n]开始,先判断这个区间Llen是否满足,再判断这个区间的左子区间的最长合法区间是否满足,再判断这个区间的Llen的Rlen加上Rlen的Llen是否满足(可能有些绕口,要耐心理解),若前三个都不满足则只有可能在Rlen中.(注意删除时要用到懒惰记号).

维护的过程在两个区间合并时体现,一个区间的Llen为其左子区间的Llen,若其左子区间的Llen长度等于其左子区间的长度.其Rlen同理.其中最长合法区间为其左子区间的最长合法区间与其右子区间的最长合法区间还有其左子区间的Rlen+其右区间的Llen三者中的最大值.

AC代码:

#include<cstdio>
#define M 50005
int max(int x,int y){return x>y?x:y;}
struct node{
	int L,R,Len,Llen,Rlen,add;//add为懒惰标记 Len为其最长合法区间 
	void clear(){//此区间清0 要设置懒惰标记使其子区间也清0 
		Llen=Rlen=Len=0;
		add=-1;
	}
	void Init(){//此区间全部重置(即房间全部空余) 
		Llen=Rlen=Len=R-L+1;
		add=1;
		return;
	}
	int size(){
		return R-L+1;
	}	
};
struct Segment{
	node tree[M<<2];
	void Up(int p){
		tree[p].Len=max(tree[p<<1].Len,tree[p<<1|1].Len);
		tree[p].Llen=tree[p<<1].Llen;
		tree[p].Rlen=tree[p<<1|1].Rlen;
		if(tree[p<<1].Llen==tree[p<<1].size())tree[p].Llen+=tree[p<<1|1].Llen;
		if(tree[p<<1|1].Rlen==tree[p<<1|1].size())tree[p].Rlen+=tree[p<<1].Rlen;
		tree[p].Len=max(tree[p].Len,tree[p<<1].Rlen+tree[p<<1|1].Llen);
	}
	void Down(int p){
		if(tree[p].add==0)return;
		if(tree[p].add==1){//判断懒惰标记 
			tree[p<<1].Init();
			tree[p<<1|1].Init();
		}else tree[p<<1].clear(),tree[p<<1|1].clear();
		tree[p].add=0;
	}
	void Build(int L,int R,int p){
		tree[p].L=L,tree[p].R=R;
		tree[p].Init();
		tree[p].add=0;//这样可以省时间 
		if(L==R)return;
		int mid=L+R>>1;
		Build(L,mid,p<<1);
		Build(mid+1,R,p<<1|1);
	}
	void Come(int L,int R,int p){
		if(tree[p].L==L&&tree[p].R==R){
			tree[p].clear();
			return;
		}
		Down(p);//向子区间传递信息 
		int mid=tree[p].L+tree[p].R>>1;
		if(R<=mid)Come(L,R,p<<1);
		else if(L>mid)Come(L,R,p<<1|1);
		else Come(L,mid,p<<1),Come(mid+1,R,p<<1|1);
		Up(p);//从子区间收集信息 
	}
	void Back(int L,int R,int p){
		if(tree[p].L==L&&tree[p].R==R){
			tree[p].Init();
			return;
		}
		Down(p);
		int mid=tree[p].L+tree[p].R>>1;
		if(R<=mid)Back(L,R,p<<1);
		else if(L>mid)Back(L,R,p<<1|1);
		else Back(L,mid,p<<1),Back(mid+1,R,p<<1|1);
		Up(p);
	}
	int Find(int d,int p){
		if(tree[p].Llen>=d)return tree[p].L;//看其区间内的包含其左端点的最长合法区间 
		Down(p);//注意此时要向子区间传递信息 
		if(tree[p<<1].Len>=d)return Find(d,p<<1);//找其左子区间
		if(tree[p<<1].Rlen+tree[p<<1|1].Llen>=d)return tree[p<<1].R-tree[p<<1].Rlen+1;//判断左子区间的Rlen+其右子区间的Llen 
		return Find(d,p<<1|1);//若都不行则可行解一定在他右子区间内 
	}
}S;
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	S.Build(1,n,1);
	while(m--){
		int a,b,flag;
		scanf("%d",&flag);
		if(flag==1){
			scanf("%d",&a);
			if(S.tree[1].Len>=a){
				int ans=S.Find(a,1);
				printf("%d\n",ans);
				S.Come(ans,ans+a-1,1);
			}
			else puts("0");
		}else{
			scanf("%d%d",&a,&b);
			S.Back(a,a+b-1,1);
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值