NKOJ-2052 座位

P2052【USACO 2013 January Gold】座位
时间限制 : - MS   空间限制 : 165536 KB
评测说明 : 时限2000ms
问题描述

奶牛们开了一家餐馆。该餐馆里有N(1 <= N <= 500,000)个排成一列的座位(编号1到N),编号越小的座位越靠近窗户。早晨开业时,座位都是空的。

今天餐馆里发生了M(1 <= M <= 300,000)个事件,这些事件总共分两类:
1.一伙人一起来就餐,该伙人共P(1 <= p <= N)个人,这伙人想坐在一段连续的位置上就餐,如果能坐下,他们希望座位尽量靠近窗户。如果无法坐下,他们会马上离开。
2.坐在a号到b号(1 <= a <= b <= N) 这连续一段座位的客人用餐完毕后离开了。

请帮贝西计算今天有多少伙人因为没有满足他们要求的座位而离开了。

输入格式

第一行,两个空格间隔的整数N和M
接下来M行,按时间先后描述了今天发生的事件:
字母A和一个整数P表示一伙P个人到达的餐馆。
字母L和两个整数a,b表示a到b这一段位置的客人离开了。

输出格式

一个整数,表示所求的结果

样例输入

10 4
A 6
L 2 4
A 5
A 2

样例输出

1

来源  感谢Formiko提供一组坑爹数据卡了一帮人

为什么奶牛开的餐厅,顾客却是人

题解

思路

首先,这是一道线段树的题目
其次,这个题目涉及到了连续区间的问题
再其次,这道题目涉及到了连续区间的修改问题(老板没讲过怎么办)

我们先来解决上面的问题
线段树和连续区间我相信没什么难的,看过PPT应该就懂了(由于这个题解是针对某个人的,所以TA知道的东西我就不多说)

主要解决一下连续区间的修改

修改连续区间(更新区间)

首先,最大连续区间要涉及到三个参数

从区间左边开始的最大连续空区间长度
从区间右边开始的最大连续空区间长度
整个区间的最大连续空区间长度

对于整个区间的最大连续空区间的长度的求法

[l,r]对应的两个子区间为[l,mid]和[mid+1,r]
那么这个区间的最大连续空区间的
    要么在[l,mid],要么在[mid+1,r],要么一半在[l,mid],一半在[mid+1,r]
对于最大空区间一半在左子区间,一半在右子区间的情况,它的最大子区间长度为
    左子区间的右边起最大连续空区间的长度+右子区间的左边起最大连续空区间的长度

******
举例
    区间坐标1 2 3 4 5 6 7 8
    区间容量1 1 0 0 0 0 1 1

    它的左子区间是[1,4],右子区间为[5,8]
    对于左子区间
        le(left_empty)=0
        re(right_empty)=2
        me(max_empty)=2
    对于右子区间
        le=2
        re=0
        me=2
    对于整个区间
        le=左子区间的le=0
        re=右子区间的re=0
        me=左子区间的re+右子区间的le=4

当然这只是针对最大连续空区间在中间的情况
******

所以对于整个区间
    me=max(左子区间的me,右子区间的me,左子区间的re+右子区间的le)

对于整个区间的左起最长空区间的长度的求法

[l,r]的条件同上

******
举例

例1
    区间坐标1 2 3 4 5 6 7 8
    区间容量0 0 1 1 1 1 1 1

    le=左子区间的le=2

例2
    区间坐标1 2 3 4 5 6 7 8
    区间容量0 0 0 0 0 0 1 1

    le=左子区间的le+右子区间的le(因为左子区间的le等于了它的长度)

因此,对于整个区间的左起最长空区间的长度,分两种情况

①左子区间的le<左子区间长度
    le=左子区间的le
②左子区间的le=左子区间长度
    le=左子区间的le+右子区间的le

右起最长空区间求法同上

修改区间分为两个部分,一个是修改区间的容量,一个是更新区间的数据(连续的空位子)

修改某段区间的容量就不需要赘述了,原理和正常的线段树一样
比如把[ll,rr]这段区间全部改为k,就是判断两个东西

①[l,r]如果在要修改的区间内,那么就直接修改[l,r]的标记为k
②[l,r]如果与修改区间有交集,那么就对[l,r]这段区间进行细分,直到细分后的区间完全在被修改区间内

那么对于更新区间数据,我们就要在以上操作的基础上加一个更新操作
更新坐了人[l,r]的最大空区间的长度,同样是判断两个东西

①[l,r]如果在修改区间[ll,rr]内,那么就把[l,r]的le,re,me都更新为0(都坐满了人)
②[l,r]仅与修改区间存在交集,那么对[l,r]进行细分,并且在细分完成之后进行上述的更新

这样就完成了这个连续区间的修改

然后就涉及到了每次修改哪段区间的问题

题目只告诉我们来了多少人,所以我们就需要自己去求修改的区间

那么如何判断呢??

对于来的人数COME,由于他们要做成一排,所以他们所在的区间的me(最长连续空区间)的大小要大于COME
又由于他们要尽量靠窗坐,所以如果存在该区间内存在多个满足条件的空缺,就要先填充靠左的区间

所以我们判断(寻找)修改的区间的方法就是

对于父区间ori=[l,r],如果me[ori]>=COME,就先判断左子区间ls(left_son),再判断右子区间rs(right_son)

具体操作如下
    若me[ori]>=COME
        ①me[ls]>=COME时,我们就向下细分,进一步判断左子区间,并跳过下面的步骤
        ②ori的中间部分的最大连续空区间(re[ls]+le[rs])>=COME时,直接求出这段区间的起点(mid-re[ls]+1)【mid为区间中点】,并跳过下面的步骤
        ③me[rs]>=COME时,向下细分,进一步判断右子区间
    由于细分到最后,得到的结果一定是在第二步结束的,所以区间的起点一定是能够得到的(这个很好想,可以自己思考以下)

    若me[ori]<COME 就完全没有搞头了

那么区间的起点得到了,区间的终点自然而然就是 起点s+区间长度COME-1

然后这个问题就解决了

注意

线段树最需要注意的就是细节问题,大概就是
①区间细分的极限是[l,r]中l==r的时候
②各种区间的起点、终点和区间长度的计算
③更新的条件,PUTDOWN函数的条件,以及区间的修改条件(可能互相之间处理不好就会有冲突)

附上代码

#include <iostream>
#include <cstdio>
#define mid (l+r>>1)
using namespace std;

inline int input()
{
    char c=getchar();int o;
    while(c>57||c<48)c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return o;
}

int ADD,COME,ll,rr;
int le[10001234],re[10001234],emp[10001234],wait[10001234],a[10001234],b[10001234];

int maxi(int A,int B,int C)
{
    if(A>=C&&A>=B)return A;
    if(B>=A&&B>=C)return B;
    return C;
}

void BT(int ori,int l,int r)
{
    a[ori]=l;b[ori]=r;
    emp[ori]=le[ori]=re[ori]=r-l+1;
    if(l<r)BT(ori<<1,l,mid),BT(ori<<1|1,mid+1,r);
}

void PD(int ori)
{
    int ls=(ori<<1),rs=(ori<<1|1);
    wait[ls]=wait[rs]=wait[ori];
    emp[ls]=le[ls]=re[ls]=wait[ori]==1?0:b[ls]-a[ls]+1;
    emp[rs]=le[rs]=re[rs]=wait[ori]==1?0:b[rs]-a[rs]+1;
    wait[ori]=0;
}

void clear(int ori)
{
    int l=a[ori],r=b[ori];
    if(ll<=l&&r<=rr){emp[ori]=le[ori]=re[ori]=(ADD==1)?0:r-l+1;wait[ori]=ADD;return;}
    int ls=(ori<<1),rs=(ori<<1|1);
    if(wait[ori])PD(ori);
    if(ll<=b[ls]&&rr>=l)clear(ls);
    if(ll<=r&&rr>=a[rs])clear(rs);
    le[ori]=le[ls]+(le[ls]==mid-l+1?le[rs]:0);re[ori]=re[rs]+(re[rs]==r-mid?re[ls]:0);
    emp[ori]=maxi(emp[ls],emp[rs],re[ls]+le[rs]);
}

void find(int ori)
{
    if(wait[ori])PD(ori);
    int ls=(ori<<1),rs=(ori<<1|1);
    if(emp[ls]>=COME){find(ls);return;}
    if(re[ls]+le[rs]>=COME){ll=b[ls]-re[ls]+1;rr=ll+COME-1;return;}
    if(emp[rs]>=COME){find(rs);return;}
}

int main()
{
//  freopen("In.txt","r",stdin);
//  freopen("True.txt","w",stdout);
    char C;
    int n=input(),m=input(),ori,res=0;
    BT(1,1,n);
    ori=(1+n)|(1!=n);
    for(int i=1;i<=m;i++)
    {
        C=getchar();
        if(C=='A')
        {
            COME=input();ADD=1;
            if(emp[1]>=COME)find(1),clear(1);
            else res++;
        }
        else
        {
            ADD=2;ll=input();rr=input();
            clear(1);
        }
    }
    cout<<res;
}

以及对拍的造数据代码

#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<iostream>
using namespace std;
int main()
{
    freopen("In.txt","w",stdout);
    srand(time(NULL));
    int CZ,s,e,n=rand()%500000+1,m=rand()%300000+1;
    printf("%d %d\n",n,m);
    for(int i=1;i<=m;i++)
    {
        CZ=rand()%2;
        if(CZ)printf("A %d\n",rand()%n);
        else
        {
            s=rand()%n;e=rand()%(n-s-1)+s+1;
            printf("L %d %d\n",s,e);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>