【SJTUOJ笔记】P1122 二哥开房间

https://acm.sjtu.edu.cn/OnlineJudge/problem/1122
Hint已经告诉我们,这是一道线段树题。其实,此题确实展示了线段树的基本功能之一:维护01序列中区间最大连续0的长度。
最普通的线段树结点中有lr两个成员,表示该结点对应区间 [l,r] [ l , r ] 。添加不同的成员可以维护不同的属性。在本题中,我们要维护01序列中最大连续0的长度,那么需要添加:

  • lsum,表示以l为起点的连续0的最大长度;
  • rsum,表示以r为终点的连续0的最大长度;
  • sum,表示区间 [l,r] [ l , r ] 中连续0的最大长度;
  • lazy,懒惰标记。lazy=0表示无标记,lazy=1表示区间被占用,lazy=2表示区间被回收。

接下来考虑如何维护这几个成员。显然对于叶子节点,几个sum属性要么全为0,要么全为1。而对于非叶子节点,我们用pushUp()函数来更新。

void pushUp(int x){ //x是要更新的结点编号
    //置为初始值
    a[x].lsum = a[x << 1].lsum; //左连续零等于左儿子的左连续零
    a[x].rsum = a[x << 1 | 1].rsum; //右连续零等于右儿子的右连续零
    int mid = (a[x].l + a[x].r) >> 1;
    if (a[x << 1].lsum == mid - a[x].l + 1) //左儿子全是零,那么左连续零应该再加上右儿子的左连续零
        a[x].lsum += a[x << 1 | 1].lsum;
    if (a[x << 1 | 1].rsum == a[x].r - mid) //同理
        a[x].rsum += a[x << 1].rsum;
    a[x].sum = max(a[x << 1].rsum + a[x << 1 | 1].lsum, max(a[x << 1].sum, a[x << 1 | 1].sum)); 
    //根据最大连续零出现的位置有三种可能
    //以上三项依次为:跨左右儿子,仅在左儿子,仅在右儿子
    //跨左右儿子的情况,必定是左儿子的右连续零加上右儿子的左连续零
}

然后是询问操作。根结点的sum表示的是整个区间的最大连续零,先判断有没有可能分配。能分配,再从根开始往下二分,每次尽可能向左儿子移动。

int query(int x, int len){
    if (a[x].l == a[x].r)
        return a[x].l;
    pushDown(x); //先下推lazy标记
    int mid = (a[x].l + a[x].r) >> 1;
    if (a[x << 1].sum >= len) //左儿子可以分配,必定向左走
        return query(x << 1, len);
    else if (a[x << 1].rsum + a[x << 1 | 1].lsum >= len) //中间可分配
        return mid - a[x << 1].rsum + 1;
    else //只能向右分配
        return query(x << 1 | 1, len);
}

在分配和释放一段序列后,需要打上lazy标记。lazy标记的维护方法和其他种类的标记相同。lazy=1对应的操作是把lsumrsumsum全部置0;而lazy=2对应的操作是吧lsumrsumsum全部置为区间长度。具体细节不再赘述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值