poj 3667 Hotel 线段树区间合并

题目链接:http://poj.org/problem?id=3667

题意:很多群人去住宾馆,他们想尽可能住在连在一起的房间,每次安排房间的时候,如果有连续足够多的房间,则从房间号最小的开始安排,输出这个房间号,如果没有连续的那么多的房间,那么输出0,而这些人分开住,但是也是尽可能连续,房间号尽可能小……然后有人离开宾馆,也是一个区间的人都离开……

终于做到了线段树区间合并系列,刚开始看了这个题完全不知道怎么做,也看了不少人的解题报告,没能明白,最后依然回归看notonlysuccess大神的源码,才有了感觉

首先设计状态

一个结构体里有四个属性

l表示该区间最左边连续的房间的数量,r表示最右边

max表示该区间内连续房间的最大数,cov表示该区间的入住情况,0表示没人住,1表示住满 ,-1表示子区间的信息已经被更新(防止重复更新子区间)如果是-1就不push_down

下面说说push_down和push_up这两个最关键的函数:

push_up就是根据两个子区间,算出当前区间的l,r,max,如果左儿子的l等于左儿子的长度,即左儿子全没人住,那么父亲的l就是左儿子的l加上右儿子的l,否则父亲的l就是左儿子的l…………父亲的r算法类似,那么父亲的max?可能是左儿子的max,右儿子的max,也可能是左儿子的r加上右儿子的l拼起来

void push_up(int l,int r,int rt)
{
    p[rt].l = p[rt<<1].l;
    p[rt].r = p[rt<<1|1].r;
    int mid = r+l>>1;
    if(p[rt].l==mid-l+1) p[rt].l+=p[rt<<1|1].l;
    if(p[rt].r==r-mid) p[rt].r+=p[rt<<1].r;
    p[rt].max = max(max(p[rt<<1].max,p[rt<<1|1].max),p[rt<<1].r+p[rt<<1|1].l);
}
push_down

如果当前节点的cov是-1则不进行

如果是0,那么就是说两个子区间都应该清空

如果是1,那么两个子区间都应该填满

更新完之后需要把cov置成-1

每次有人来住的时候得先判断人数和p[1].max,如果大于,说明没有连续的房间,输出0,否则,就query出最左边的房间号,然后根据这个房间号再来update一个区间

如果是有人离开宾馆,则直接update即可

具体的看函数就很明了

#include <cstdio>
#include <cstring>
const int  MAX = 50010;
struct point
{
    int l,r,max,cov;
}p[MAX<<2];
int max(int a,int b)
{
    return (a>b)?a:b;
}
void push_up(int l,int r,int rt)
{
    p[rt].l = p[rt<<1].l;
    p[rt].r = p[rt<<1|1].r;
    int mid = r+l>>1;
    if(p[rt].l==mid-l+1) p[rt].l+=p[rt<<1|1].l;
    if(p[rt].r==r-mid) p[rt].r+=p[rt<<1].r;
    p[rt].max = max(max(p[rt<<1].max,p[rt<<1|1].max),p[rt<<1].r+p[rt<<1|1].l);
}
void push_down(int l,int r,int rt)
{
    if(p[rt].cov!=-1)
    {
        p[rt<<1].cov = p[rt<<1|1].cov = p[rt].cov;
        if(p[rt].cov)
        p[rt<<1].l = p[rt<<1|1].l = p[rt<<1].r = p[rt<<1|1].r = p[rt<<1].max = p[rt<<1|1].max = 0;
        else
        {
            int mid = r+l>>1;
            p[rt<<1].l = p[rt<<1].r = p[rt<<1].max = mid-l+1;
            p[rt<<1|1].l = p[rt<<1|1].r = p[rt<<1|1].max = r-mid;
        }
        p[rt].cov = -1;
    }
}
void build(int l,int r,int rt)
{
    p[rt].l = p[rt].r = p[rt].max = r-l+1;
    p[rt].cov = -1;
    if(l==r)
    {
        return;
    }
    int mid = r+l>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
}
void update(int a,int L,int R,int l,int r,int rt)
{
    if(L<=l&&R>=r)
    {
        if(a)
        p[rt].l = p[rt].r = p[rt].max = 0;
        else
        p[rt].l = p[rt].r = p[rt].max = r-l+1;
        p[rt].cov = a;
        return;
    }
    int mid = r+l>>1;
    push_down(l,r,rt);
    if(L<=mid) update(a,L,R,l,mid,rt<<1);
    if(R>mid) update(a,L,R,mid+1,r,rt<<1|1);
    push_up(l,r,rt);
}
int query(int a,int l,int r,int rt)
{
    if(l==r)
    {
        return l;
    }
    int mid = r+l>>1;
    push_down(l,r,rt);
    if(p[rt<<1].max>=a) return query(a,l,mid,rt<<1);
    if(p[rt<<1].r+p[rt<<1|1].l>=a) return mid-p[rt<<1].r+1;
    return query(a,mid+1,r,rt<<1|1);
}
int main()
{
    int n,m,k,x,y;
    while(scanf("%d%d",&n,&m)==2)
    {
        build(1,n,1);
        while(m--)
        {
            scanf("%d",&k);
            if(k==1)
            {
                scanf("%d",&x);
                if(p[1].max<x) puts("0");
                else
                {
                    int tmp = query(x,1,n,1);
                    printf("%d\n",tmp);
                    update(1,tmp,tmp+x-1,1,n,1);
                }
            }
            else
            {
                scanf("%d%d",&x,&y);
                update(0,x,x+y-1,1,n,1);
            }
        }
    }
    return 0;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值