线段树应用:区间合并

传送门luoguP2894

本题我们要维护最左边的值,考虑使用线段树维护。我们发现只用一个来存储当前的节点的值并不够,因为对于一个节点我们无法用一个来描述这整个区间哪些为空,哪些满了,例如查询的跨越了两个区间,我们就无法找到最左边的了。
所以我们考虑用多个变量描述该区间的状态:
lmx表示该区间最左边连续为空的个数,
rmx表示该区间最右边连续为空的个数,
len表示整个区间的总房间数,
sum表示整个区间的总空房间数。
这样我们可以很好的描述状态了,
对于一个节点的左房间数等于t[k].lmx,
若左儿子全空即t[lc].len=t[lc].sum,那么t[k].lmx=t[lc].sum+t[rc].lmx,
同理于右节点。
对于横跨两个节点的区间,
等于t[lc].rmx+t[rc].lmx左儿子的右房间数+右儿子的左房间数,
由于是区间更新,所以我们还要使用lazy_tag来做,
核心介绍完了,代码实现:

#include<bits/stdc++.h>
using namespace std;
#define lc k<<1
#define rc k<<1|1

const int N=5e5+5;
int n,m;
struct node
{
    
    
	int lz,sum,len,lmx,rmx,l,r;
	//该节点懒惰标记,最大连续空房间数,房间总数,左边连续空房间数,右边连续空房间数 
}t[N<<2];//4倍以上 

inline void pushdown(int k)
{
    
    
	if(t[k].lz==0) return;
	if(t[k].lz==1)//开房 
	{
    
    
		t[lc].lz=t[rc].lz=1;
		t[lc].lmx=t[lc].rmx=t[lc].sum=0;
		t[rc].lmx=t[rc].rmx=t[rc].sum=0;
	}
	if(t[k].lz==2)//退房 
	{
    
    
		t[lc].lz=t[rc].lz=2;
		t[lc].lmx=t[lc].rmx=t[lc].sum=t[lc].len;
		t[rc].lmx=t[rc].rmx=t[rc].sum=t[rc].len;
	}
	t[k].lz=0;//清空当前的懒标 
}

inline void pushup(int k)
{
    
    
	if(t[lc].sum==t[lc].len) t[k].lmx=t[lc].len+t[rc].lmx; 
	else t[k].lmx=t[lc].lmx;//如果左儿子全部空,那么父节点的左边连续最大值为左儿子加右儿子的左边 
	if(t[rc].sum==t[rc].len) t[k].rmx=t[lc].rmx+t[rc].len;//同理 
	else t[k].rmx=t[rc].rmx;
	t[k].sum=max(max(t[lc].sum,t[rc].sum),t[lc].rmx+t[rc].lmx);//整个区间连续最大值为左儿子,右儿子,跨越了左右的最大值 
}

inline void build(int k,int l,int r)
{
    
    
	t[k].l=l;t[k].r=r;//不要忘了初始化节点对于的一段区间 
	t[k].lmx=t[k].rmx=t[k].sum=t[k].len=r-l+1;//初始全是空,总的左边的右边的全部空的 
	t[k].lz=0;
	if(l==r) return;//到叶子节点找完了 
	int mid=(l+r)>>1;
	build(lc,l,mid);//建左儿子 
	build(rc,mid+1,r);//建右儿子 
}

inline void update(int k,int l,int r,int tag)
{
    
    
	pushdown(k);//下传懒标 
	if(t[k].l>=l && t[k].r<=r) //如果覆盖了这个区间 
	{
    
    
		if(tag==1) t[k].lmx=t[k].rmx=t[k].sum=0; //如果是开房,就全部更新为0 
		else t[k].lmx=t[k].rmx=t[k].sum=t[k].len;//否则全部为空 
		t[k].lz=tag;
		return;
	}
	int mid=(t[k].l+t[k].r)>>1;
	if(l<=mid) update(lc,l,r,tag); //在左区间 
	if(r>mid) update(rc,l,r,tag);//在右区间 
	pushup(k);//更新 
}

inline int query(int k,int val)
{
    
    
	pushdown(k);//下传懒标 
	if(t[k].l==t[k].r) return t[k].l;//找到了返回左节点 
	int mid=(t[k].l+t[k].r)>>1;
	if(t[lc].sum>=val) return query(lc,val);//注意:是找尽量左边的,所以先找左,再找中,再找右 
	if(t[lc].rmx+t[rc].lmx>=val) return mid-t[lc].rmx+1;
	if(t[rc].sum>=val) return query(rc,val);
}

int main()
{
    
    
	scanf("%d%d",&n,&m);
	build(1,1,n);//构建线段树 
	int x,y,cas;
	for(int i=1;i<=m;i++)
	{
    
    
		scanf("%d",&cas);
		if(cas==1)//如果开房 
		{
    
    
			scanf("%d",&x);
			if(t[1].sum>=x)//判断整个区间是否装的下,否则输出0 
			{
    
    
				int left=query(1,x);//查询左节点 
				cout<<left<<endl;
				update(1,left,left+x-1,cas);//整个区间全部满,更新为1 
			}
			else cout<<0<<endl;
		}
		else
		{
    
    
			scanf("%d%d",&x,&y);
			update(1,x,x+y-1,cas);//整个区间变空,更新为0 
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/pigonered/article/details/120909209
今日推荐