区间合并 POJ 3667 Hotel

Hotel
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 14755 Accepted: 6385
Description

The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shores of Lake Superior. Bessie, ever the competent travel agent, has named the Bullmoose Hotel on famed Cumberland Street as their vacation residence. This immense hotel has N (1 ≤ N ≤ 50,000) rooms all located on the same side of an extremely long hallway (all the better to see the lake, of course).

The cows and other visitors arrive in groups of size Di (1 ≤ Di ≤ N) and approach the front desk to check in. Each group i requests a set of Di contiguous rooms from Canmuu, the moose staffing the counter. He assigns them some set of consecutive room numbers r..r+Di-1 if they are available or, if no contiguous set of rooms is available, politely suggests alternate lodging. Canmuu always chooses the value of r to be the smallest possible.

Visitors also depart the hotel from groups of contiguous rooms. Checkout i has the parameters Xi and Di which specify the vacating of rooms Xi ..Xi +Di-1 (1 ≤ Xi ≤ N-Di+1). Some (or all) of those rooms might be empty before the checkout.

Your job is to assist Canmuu by processing M (1 ≤ M < 50,000) checkin/checkout requests. The hotel is initially unoccupied.

Input

  • Line 1: Two space-separated integers: N and M
  • Lines 2..M+1: Line i+1 contains request expressed as one of two possible formats: (a) Two space separated integers representing a check-in request: 1 and Di (b) Three space-separated integers representing a check-out: 2, Xi, and Di

Output

  • Lines 1…..: For each check-in request, output a single line with a single integer r, the first room in the contiguous sequence of rooms to be occupied. If the request cannot be satisfied, output 0.

Sample Input

10 6
1 3
1 3
1 3
1 3
2 5 5
1 6
Sample Output

1
4
7
0
5

题意:一个旅馆,有n个房间,操作为1时,查询是否有连续的num个房间可以居住,能住就输出靠左房间的标号,操作为2时更新区间[X,X+D-1]的房间的人清空。

区间合并初试。

区间合并比区间更新多了一个lsum和rsum,用来记录结点的左连续区间数值和右连续区间数值,msum则用来记录这个区间的最长连续区间。

基本上是采用区间更新的方式进行区间合并,标记操作差不多相同,因为题中0本身是个标记,所以采用-1作为未标记。然后就是Push_Up()和Push_Down(),每次操作都必须对lsum和rsum进行操作,lsum就是从最左边结点开始连续相同0的个数,rsum就是从最右边结点开始,向左连续0的个数,值得注意的是,记录msum[id]时左子树的右区间和右子树区间也可以组成连续的区间,然后就是这个区间和msum[id*2],msum[id]*2+1]比较更新msum[id]。在查询函数中也有类似的更新操作。

代码:

#include"stdio.h"
#include"iostream"
#include"algorithm"
#include"string.h"
#include"queue"
#include"math.h"
#define lson l,mid,id*2
#define rson mid+1,r,id*2+1
typedef long long LL;
const int maxn = 50000+10;
using namespace std;

int msum[maxn*4];  ///区间内最多连续的房间
int lsum[maxn*4];  ///区间内最左连续的最大值
int rsum[maxn*4];  ///区间内最右连续的最大值
int cover[maxn*4]; ///标记函数

void Push_Up(int id,int len)  ///向上更新
{
    lsum[id] = lsum[id*2];  ///id结点的最左连续区间最大 == 左子树的值
    ///如果左子树全部都被覆盖,则加上右子树的左区间
    if(lsum[id*2] == len-(len/2)) lsum[id] += lsum[id*2+1];
    rsum[id] = rsum[id*2+1];  ///id结点的最右连续区间最大 == 右子树的值
    ///如果右子树全部都被覆盖,则加上左子树的左区间
    if(rsum[id*2+1] == len/2) rsum[id] += rsum[id*2];
    ///id结点的最大值为左右区间的最大值与(左子树右连续区间+右子树的左连续区间)中的最大值
    msum[id] = max(lsum[id*2+1]+rsum[id*2],max(msum[id*2],msum[id*2+1]));
}

void Push_Down(int id,int len)  ///向下更新
{
    if(cover[id] != -1)
    {
        cover[id*2] = cover[id*2+1] = cover[id];  ///传递标记
        ///整个左子区间覆盖,标记为0代表房间空出来,可以住人,可以住连续的r-l+1个人,1就是房间被占,不能住人,整个区间值为0
        lsum[id*2] = rsum[id*2] = msum[id*2] = cover[id]?0:len-(len/2);
        lsum[id*2+1] = rsum[id*2+1] = msum[id*2+1] = cover[id]?0:len/2;  ///同上
        cover[id] = -1;   ///消除父亲节点标记
    }
}

void build(int l,int r,int id)  ///建树
{
    cover[id] = -1;  ///初始化标记
    msum[id] = lsum[id] = rsum[id] = r-l+1;
    if(l == r)
        return;
    int mid = (l+r)/2;
    build(lson);
    build(rson);
}

void update(int L,int R,int c,int l,int r,int id)  ///区间更新
{
    if(L <= l && r <= R)  ///[l,r]在[L,R]内就更新
    {
        msum[id] = lsum[id] = rsum[id] = c?0:r-l+1;  ///全部赋值0或者1
        cover[id] = c;    ///上标记
        return;
    }
    Push_Down(id,r-l+1);  ///向下更新,更新了以后才能继续更新
    int mid = (l+r)/2;
    if(L <= mid) update(L,R,c,lson);
    if(mid < R)  update(L,R,c,rson);
    Push_Up(id,r-l+1);   ///向上更新,更新完子节点更新父亲节点
}
int query(int num,int l,int r,int id)  ///查询
{
    if(l == r)
    {
        return l;
    }
    Push_Down(id,r-l+1);  ///向下更新左右子树后才能继续查询左右子树
    int mid = (l+r)/2;
    if(msum[id*2] >= num) return query(num,lson);  ///左子树有足够空间向左下走
    ///先判断左子树和右子树连接起来是否能装下num,能装下num则是左子树右区间的左边的坐标
    else if(rsum[id*2]+lsum[id*2+1] >= num) return mid-rsum[id*2]+1;
    return query(num,rson);  ///朝右走
}
int main(void)
{
    int n,q;
    while(scanf("%d %d",&n,&q) != EOF)
    {
        build(1,n,1);
        while(q--)
        {
            int op;
            scanf("%d",&op);
            if(op == 1)
            {
                int num;
                scanf("%d",&num);
                if(msum[1] >= num)
                {
                    int p = query(num,1,n,1);
                    printf("%d\n",p);
                    update(p,p+num-1,1,1,n,1);
                }
                else puts("0");
            }
            else
            {
                int L,R;
                scanf("%d%d",&L,&R);
                update(L,L+R-1,0,1,n,1);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值