Poj 3667(线段树,查找连续区间,区间合并)(经典)

problem

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


思路

简化题意即为:

两种操作
1.找是否存在指定长度的空的(未标记的)区间,如果有,输出最左端的点,并将这个区间标记
2.从一指定点开始,消除一段指定长度的区间的标记

看到这题很容易想到线段树,其常用来维护区间的信息。
其中操作2消除标记就是最基本的区间覆盖
关键问题是操作1怎么处理
如果只是让判断是否存这样连续未标记的区间,那我只要再维护一个数组,比如slen,它表示当前结点最大连续未标记长度。在pushup和pushdown时维护一下即可。
令人头疼的是,它不仅要判断是否存在足够长度未标记的区间,还要找出最左端的位置


这时候我没想到怎么做,看了一下别人的思想,顿有醍醐灌顶之感

维护三个信息,分别是:
lsum 当前区间从左端点开始向又看,最大连续未标记区间是多长
rsum 当前区间从右端点开始向左看,最大连续未标记区间是多长
msum 递归定义:max{ 左半区间msum , 右半区间msum , 左半区间的rsum+右半区间lsum(这两部分正好拼在一起)}
比如区间6~10
标记是10011 那lsum=0 rsum=0 msum=2 ;
标记是00111 那lsum=2 rsum=0 msum=2 ;

这样定义的msum即为当前结点表示区间内的最大未标记连续区间


对于lsum和rsum的维护,核心代码为:

lsum[rt]=lsum[rt<<1];//当前区间左大连续自然等于左儿子左大连续
rsum[rt]=rsum[rt<<1|1];//当前区间右大连续自然等于右儿子右大连续
//但这样是极限吗?显然不是 如果左儿子全未标记 可能右儿子的左部分也未标记 就有了下面的代码
if(lsum[rt]==len-(len>>1)) lsum[rt]+=lsum[rt<<1|1];//if条件是:左大连续等于左区间长 加上右儿子左大连续
if(rsum[rt]==(len>>1)) rsum[rt]+=rsum[rt<<1];//同理


代码示例

#include<iostream>
#include<cstdio>
#include<algorithm>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define bug cout<<"test"<<endl
using namespace std;
const int maxn=50100;

int lsum[maxn<<2],rsum[maxn<<2],msum[maxn<<2];
int cov[maxn<<2];//-1 0 1三种状态

void PushDown(int rt,int len){
    if(cov[rt]!=-1){
        cov[rt<<1]=cov[rt<<1|1]=cov[rt];
        msum[rt<<1]=lsum[rt<<1]=rsum[rt<<1]=cov[rt]?0:len-(len>>1);
        msum[rt<<1|1]=lsum[rt<<1|1]=rsum[rt<<1|1]=cov[rt]?0:len>>1;
        cov[rt]=-1;
    }
}

void PushUp(int rt,int len){
    lsum[rt]=lsum[rt<<1];
    rsum[rt]=rsum[rt<<1|1];
    if(lsum[rt]==len-(len>>1)) lsum[rt]+=lsum[rt<<1|1];
    if(rsum[rt]==(len>>1)) rsum[rt]+=rsum[rt<<1];
    msum[rt]=max(max(msum[rt<<1],msum[rt<<1|1]),rsum[rt<<1]+lsum[rt<<1|1]);
}

void build(int l,int r,int rt){
    msum[rt]=lsum[rt]=rsum[rt]=r-l+1;//一开始均为区间长度
    cov[rt]=-1;
    if(l==r) return ;
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}

void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l&&r<=R){
        msum[rt]=lsum[rt]=rsum[rt]=c?0:r-l+1;
        cov[rt]=c;
        return ;
    }
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(L<=m) update(L,R,c,lson);
    if(m<R) update(L,R,c,rson);
    PushUp(rt,r-l+1);
}

int query(int w,int l,int r,int rt){
    if(l==r) return l;
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(msum[rt<<1]>=w) return query(w,lson);//先考虑左儿子
    else if(rsum[rt<<1]+lsum[rt<<1|1]>=w) return m-rsum[rt<<1]+1;//再考虑两部分合并的中间部分
    return query(w,rson);//最后考虑右儿子
}

int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    build(1,n,1);
    while(m--){
        int op,a,b;
        scanf("%d",&op);
        if(op==1){
            scanf("%d",&a);
            if(msum[1]<a) puts("0");//最大空出长度都不满足
            else{
                int p=query(a,1,n,1);
                printf("%d\n",p);
                update(p,p+a-1,1,1,n,1);
            }
        }

        else{
            scanf("%d %d",&a,&b);
            update(a,a+b-1,0,1,n,1);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值