POJ 3667 Hotel(线段树区间合并)

题意:

给出n个房间,m个操作,操作有两种
操作1:需要x个连续的房间,如果存在则输出开始的房间,并占用,要求尽量小,没有的话则输出0.
操作2:从x开始的连续y个房间,解除占用。

关于区间合并:

区间合并问题一般是要求满足条件的一段连续的区间,个人觉得这类问题一个精华所在就是线段树的pushup函数的写法,如下:

/*ls表示区间的满足条件最长连续前缀,rs表示最长连续后缀,
ms表示该区间的最长连续序列。*/
void pushup(int l,int r,int rt)
{
    int m=(r+l)>>1;
    ls[rt]=ls[rt<<1];
    if(ls[rt<<1]==m-l+1) ls[rt]+=ls[rt<<1|1];
    rs[rt]=rs[rt<<1|1];
    if(rs[rt<<1|1]==r-m) rs[rt]+=rs[rt<<1];
    ms[rt]=max(max(ms[rt<<1],ms[rt<<1|1]),rs[rt<<1]+ls[rt<<1|1]);
    //在左边区间还是右边区间或者是跨区间
}

在这类问题种我们需要维护三个值,ls、rs、ms数组。我们要求的是区间内的最长连续序列,跟它的前缀和后缀又有什么关系呢?这就涉及到区间合并了。
一段区间的最长连续序列有三种情况:
第一种:最长连续序列起点和终点都在左区间,也就是只跟左区间有关。ms[rt<<1] 是一个候选人。
第二种:都在右区间。ms[rt<<1|1]是一个候选人。
第三种:起点在左区间,终点在右区间,那么这个区间长度就是左区间后缀加上右区间前缀,就用到了前缀和后缀。
对于每一个结点,都按照这样的方式去维护三个值,这样的方法就是解决这类问题的基本方法了。

思路:

本题其实就是求区间最长连续0序列。
修改操作是比较好实现的,解除占用只要将区间的ls、rs、ms修改为本区间的长度即可,因为区间全部变为了0,以上三个值都是区间长度。
接下来就是询问操作,首先将区间的最长连续序列与所需要的连续房间相比较,如果ms[1],就是整个区间的最长连续序列都不能满足,直接输出0.如果可以那么一定有区间可以满足。
依然是三种情况,因为要求房间最小,从左到右判断。
先是左区间满不满足,满足则深入左区间,答案就在左区间。
否则就是跨区间,如果满足可以直接返回第一个房间的位置。
然后就是右区间。
递归的结束是跨区间,因为每一个连续的区间都可以分成跨区间或者就是l==r这个情况是开一个房间,这里讲的有点迷,写的时候讨论所有情况这也就自然写出来了。

代码:

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef long long ll;
const int maxn=5e4+10;
int ls[maxn<<2],rs[maxn<<2],ms[maxn<<2],col[maxn<<2];
int n,m;
/*区间合并正常pushup*/
void pushup(int l,int r,int rt)
{
    int m=(r+l)>>1;
    ls[rt]=ls[rt<<1];
    if(ls[rt<<1]==m-l+1) ls[rt]+=ls[rt<<1|1];
    rs[rt]=rs[rt<<1|1];
    if(rs[rt<<1|1]==r-m) rs[rt]+=rs[rt<<1];
    ms[rt]=max(max(ms[rt<<1],ms[rt<<1|1]),rs[rt<<1]+ls[rt<<1|1]);
}
void pushdown(int l,int r,int rt)
{
    int m=(r+l)>>1;
    if(col[rt]!=-1)
    {
        /*分0和1两种情况*/
        col[rt<<1]=col[rt<<1|1]=col[rt];
        if(col[rt]==0)
        {
            ls[rt<<1]=rs[rt<<1]=ms[rt<<1]=0;
            ls[rt<<1|1]=rs[rt<<1|1]=ms[rt<<1|1]=0;
        }
        else if(col[rt]==1)
        {
            ls[rt<<1]=rs[rt<<1]=ms[rt<<1]=m-l+1;
            ls[rt<<1|1]=rs[rt<<1|1]=ms[rt<<1|1]=r-m;
        }
        col[rt]=-1;//记得修改为-1
    }
}
void build(int l,int r,int rt)
{
    col[rt]=-1;
    int m=(r+l)>>1;
    if(l==r)
    {
        ls[rt]=rs[rt]=ms[rt]=1;
        return;
    }
    build(lson);
    build(rson);
    pushup(l,r,rt);//向上更新
    return;
}
/*正常区间修改*/
void updata(int ql,int qr,int c,int l,int r,int rt)
{
    int m=(r+l)>>1;
    if(ql<=l&&r<=qr)
    {
        ls[rt]=rs[rt]=ms[rt]=c*(r-l+1);//乘上区间长度
        col[rt]=c;
        return;
    }
    pushdown(l,r,rt);
    if(ql<=m) updata(ql,qr,c,lson);
    if(m<qr) updata(ql,qr,c,rson);
    pushup(l,r,rt);
}
/*因为要找最左边的,所以先看左区间的最大值是不是符合如果符合就深入左区间。
如果不符合就看跨区间的情况。
再不行就深入右区间。
如果w=1的话会搜索到l==r。递归结束条件l==r或者跨区间*/
int query(int w,int l,int r,int rt)
{
    int m=(r+l)>>1;
    if(l==r) return l;
    pushdown(l,r,rt);
    if(ms[rt<<1]>=w) return query(w,lson);//,除了1个的情况,总会转到跨区间的情况
    else if(rs[rt<<1]+ls[rt<<1|1]>=w) return m-rs[rt<<1]+1;
    return query(w,rson);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        int op;
        scanf("%d",&op);
        if(op==1)
        {
            int d;
            scanf("%d",&d);
            //printf("ms[1]:%d\n",ms[1]);
            if(ms[1]<d) printf("0\n");
            else
            {
                 int p=query(d,1,n,1);
                 printf("%d\n",p);
                 updata(p,p+d-1,0,1,n,1);
            }
        }
        else if(op==2)
        {
            int x,d;
            scanf("%d%d",&x,&d);
            updata(x,x+d-1,1,1,n,1);
            //printf("ms[1]:%d\n",ms[1]);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44607936/article/details/99626586