[线段树]P2894 [USACO08FEB]Hotel G

C++实现房间操作的线段树算法
博客围绕房间操作问题展开,给定n个房间和m种操作,包括查询连续空房并入住、退房。采用区间操作的线段树解决,需维护sum、lmax、rmax值及lazy标记,介绍了向下更新、向上更新、修改和查询操作,最后提及主函数。

链接

题意

给你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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值