线段树--合并区间

引入

合并区间其实就是在线段树–lazy标记的基础上,每个点储存多个区间信息。在修改时就需要合并区间。

例题

P2894 [USACO08FEB]Hotel G

分析

住店和退房其实就是修改区间,但怎么查找连续为0的区间呢?这就需要合并区间了。
我们可以用lm[k]表示从左数连续为0的长度,rm[k]表示从右数连续为0的区间长度,m[k]表示区间[l~r]的连续为0的长度。
如下丑图:
在这里插入图片描述
那么我们就可以得到m从哪几个区间转移过来了。(详见代码)
但还有一个问题,怎么求出最小的编号呢?
显然,当满足条件的位置是rm[2k]+lm[2k+1]时,最小位置为mid-rm[k*2]+1。

代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 50000+5
int n;
int m[4*N],lm[4*N],rm[4*N],lazy[4*N];
void built(int k,int l,int r)
{
    lazy[k]=-1;
    lm[k]=rm[k]=m[k]=r-l+1;//初始化三个数组 
    if(l==r) return;
    int mid=(l+r)>>1;
    built(k*2,l,mid);
    built(k*2+1,mid+1,r);
}
void pushdown(int k,int l,int r)
{
    if(lazy[k]==-1) return;
    int mid=(l+r)>>1;
    lm[k*2]=rm[k*2]=m[k*2]=(mid-l+1)*lazy[k];//区间整整体改变,三个数组也一起变 
    lm[k*2+1]=rm[k*2+1]=m[k*2+1]=(r-mid)*lazy[k];
    lazy[k*2]=lazy[k*2+1]=lazy[k];
    lazy[k]=-1;
}
int ask(int k,int l,int r,int len)
{
    if(l==r)
    {
        if(len==1) return l;
        else return 0;
    }
    int mid=(l+r)>>1;
    pushdown(k,l,r);
    if(m[k*2]>=len) return ask(k*2,l,mid,len);//题目要求编号最小,则尽可能向左 
    if(rm[k*2]+lm[k*2+1]>=len) return mid-rm[k*2]+1;//在中间,可以直接求出位置 
    return ask(k*2+1,mid+1,r,len);//向右找 
}
void update(int k,int l,int r,int x,int y,int v)
{
    if(x>r||y<l) return;
    if(x<=l&&r<=y)
    {
        lm[k]=rm[k]=m[k]=(r-l+1)*v;
        lazy[k]=v;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(k,l,r);
    update(k*2,l,mid,x,y,v);
    update(k*2+1,mid+1,r,x,y,v);
    if(lm[k*2]==mid-l+1) lm[k]=lm[k*2]+lm[k*2+1];//左儿子连上了右儿子 
    else lm[k]=lm[k*2];
    if(rm[k*2+1]==r-mid) rm[k]=rm[k*2]+rm[k*2+1];//右儿子全是1,与左儿子相连 
    else rm[k]=rm[k*2+1];
    int maxn;
    maxn=max(m[k*2],m[k*2+1]);//找儿子 
    maxn=max(maxn,rm[k*2]+lm[k*2+1]);//中间 
    maxn=max(maxn,max(lm[k],rm[k]));//左右取max 
    m[k]=maxn;
}
int main()
{
//  freopen("「USACO2008FEB」Hotel.in","r",stdin);
//  freopen("「USACO2008FEB」Hotel.out","w",stdout);
    int q;
    scanf("%d%d",&n,&q);
    built(1,1,n);
    for(int i=1; i<=q; i++)
    {
        int flag;
        scanf("%d",&flag);
        if(flag==1)
        {
            int d;
            scanf("%d",&d);
            int ans=ask(1,1,n,d);
            printf("%d\n",ans);
            if(ans) update(1,1,n,ans,ans+d-1,0);//用房间就入住 
        }
        else
        {
            int x,d;
            scanf("%d%d",&x,&d);
            update(1,1,n,x,x+d-1,1);//退房 
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值