题意
给你n个房间,编号从1-b,然后是m中操作,1 x,表示查询所有房间中有没有连续x个房间是开着的,如果有,输出最左边的编号,编号要尽量小,然后将这x房间全不入住,如果不存在就输出0。2 x y,表示将房间号为x–x + y - 1的房间退房。
思路
一个区间操作的线段树,首先看一下需要维护的值,因为是要找连续的空房,那么我们肯定要有一个sum来记录某段区间内的连续的空房数量,然是仅仅维护这一个值是肯定不够的,他不能是tr[u].sum = tr[ul].sum + tr[ur].sum,因为题目中说的是连续的,如果是这种情况,一共有七个房间,左边的连续三个房间是1 2 3,右边的连续房间是5 6 7,其他的都是入住了的,这个时候的sum值是3,而不是6。所以这个时候我们还需要两个值,lmax和rmax,分别代表,一段区间内,以左端点开头的连续空房数和以左端点结尾的最大空房数。应为这里存在着开房和退房,所以我们需要一个lazy标记来记录,所以结构体如下:
struct node
{
int l, r;
int len;
int laz;
int sum;
int lmax, rmax;
}tr[N << 2];
既然存在lazy标记,那么我们就会向下更新,现在来看一下向下更新的操作:
这里存在三种情况,laz = 0, laz = 1, laz = 2,0我们就不说了,当为1的时候(1表示开房,2表示退房),如果是开房的话,这段区间内的所有房间都会被占用,那么这段区间的sum,lmax,rmax 都是0了,如果laz=2,那么这段区间内的房间都会空闲,那么一段区间内的lmax,rmax和sum就等于这段区间的长度:
void pushdown(int u)
{
if (tr[u].laz == 0) return ;
if (tr[u].laz == 1)
{
///1表示开房
tr[ul].laz = tr[ur].laz = tr[u].laz;
tr[ul].sum = tr[ul].lmax = tr[ul].rmax = 0;
tr[ur].sum = tr[ur].lmax = tr[ur].rmax = 0;
}
if (tr[u].laz == 2)
{
tr[ul].laz = tr[ur].laz = tr[u].laz;
tr[ul].sum = tr[ul].lmax = tr[ul].rmax = tr[ul].len;
tr[ur].sum = tr[ur].lmax = tr[ur].rmax = tr[ur].len;
}
tr[u].laz = 0;
}
既然要更新一段区间的sum,那么我们就要有向上更新:
我们考虑一下sum的值,sum的值与它的左右儿子区间有关,因为我们是根据左右儿子的值来进行更新的,那么我们就要单独考虑左右儿子区间了,这里我们考虑左儿子区间,右儿子区间同理。左儿子区间分两种情况,1.整个区间可能都是空着的,那么就是tr[ul].sum == tr[u].len,这样我们就表示了左儿子区间内的房子全是空着的,那么这个时候tr[u].lmax = tr[ul].len + tr[ur].lmax,2.左儿子的区间内的房子不全是空这个,那么这个时候tr[u].lmax = tr[u].lmax。右儿子区间同理。那么现在我们该考虑u这个区间的sum值了,这里是三个值求最大,tr[ul].sum,tr[ur].sum,tr[ul].rmx + tr[ur].lmax,细想一下就明白了。
void pushup(int u)
{
if (tr[ul].sum == tr[ul].len)
tr[u].lmax = tr[ul].len + tr[ur].lmax;
else
tr[u].lmax = tr[ul].lmax;
if (tr[ur].sum == tr[ur].len)
tr[u].rmax = tr[ur].len + tr[ul].rmax;
else
tr[u].rmax = tr[ur].rmax;
tr[u].sum = max(max(tr[ul].sum, tr[ur].sum), tr[ul].rmax + tr[ur].lmax);
}
然后是修改操作,修改操作就是跟模板一样的,只是要注意一下是退房开始开房。
void modify(int u, int l, int r, int tag)
{
if (l <= tr[u].l && r >= tr[u].r)
{
if (tag == 1)
tr[u].sum = tr[u].lmax = tr[u].rmax = 0;
else
tr[u].sum = tr[u].lmax = tr[u].rmax = tr[u].len;
tr[u].laz = tag;
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(ul, l, r, tag);
if (r > mid) modify(ur, l, r, tag);
pushup(u);
}
接下来是查询操作,我们要找到是最左端的区间,并且要尽量小,这里我们还是分三段来查找,左儿子,左儿子+右儿子,右二子。这三段里面都有可能存在连续的x个空房数量。
int query(int u, int l, int r, int len)
{
if (tr[u].l == tr[u].r) return tr[u].l;
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (tr[ul].sum >= len) return query(ul, l, mid, len);
if (tr[ul].rmax + tr[ur].lmax >= len) return mid - tr[ul].rmax + 1;
else return query(ur, mid + 1, r, len);
}
最后就是主函数:
void solve()
{
scanf("%d%d", &n, &m);
build(1, 1, n);
while (m --)
{
int op, x, y;
scanf("%d", &op);
if(op == 1)
{
scanf("%d", &x);
if (tr[1].sum >= x)
{
int left = query(1, 1, n, x);
printf("%d\n", left);
modify(1, left, left + x - 1, 1);///注意开房
}
else printf("0\n");
}
else
{
scanf("%d%d", &x, &y);
modify(1, x, x + y - 1, 2);
}
}
}
C++实现房间操作的线段树算法
博客围绕房间操作问题展开,给定n个房间和m种操作,包括查询连续空房并入住、退房。采用区间操作的线段树解决,需维护sum、lmax、rmax值及lazy标记,介绍了向下更新、向上更新、修改和查询操作,最后提及主函数。

被折叠的 条评论
为什么被折叠?



