poj - 3667

// 3752K    641MS   C++
#include <cstdio>
#include <cstring>

using namespace std;

#define MAX 50010

struct TreeNode {
    int left;
    int right;
    int lMax;
    int rMax;
    int Max;
    char color;
};

typedef struct TreeNode TreeNode;

TreeNode tree[MAX<<2];

void buildTree(int pos, int begin, int end) {
    TreeNode & curNode = tree[pos];
    int length = end - begin + 1;
    curNode.left = begin;
    curNode.right = end;
    curNode.Max = end - begin + 1;
    curNode.lMax = length;
    curNode.rMax = length;
    curNode.color = -1;
    if (begin != end) {
        int mid = (begin + end)>>1;
        buildTree(pos<<1, begin, mid);
        buildTree(pos<<1|1, mid+1, end);
    }
}

void pushDown(int pos) {
    TreeNode & curNode = tree[pos];
    TreeNode & leftNode = tree[pos<<1];
    TreeNode & rightNode = tree[pos<<1|1];
    if (curNode.color != -1) { // all free or all occupied
        leftNode.color = rightNode.color = curNode.color;

        leftNode.lMax = leftNode.rMax = leftNode.Max
                = leftNode.color ? 0 : (leftNode.right - leftNode.left + 1);

        rightNode.lMax = rightNode.rMax = rightNode.Max
                = rightNode.color ? 0 : (rightNode.right - rightNode.left + 1);

        curNode.color = -1;
    }
}

// 0 means impossible, else return the begin Id
int checkIn(int pos, int length) {
    TreeNode & curNode = tree[pos];
    TreeNode & leftNode = tree[pos<<1];
    TreeNode & rightNode = tree[pos<<1|1];
    int Max = curNode.Max;
    int left = curNode.left;
    int right = curNode.right;

    if (left == right) {
        return 1;
    }

    // pushDown(pos);

    if (leftNode.Max >= length) {
        return checkIn(pos<<1, length);
    } else if (leftNode.rMax + rightNode.lMax >= length) {
        return rightNode.left - leftNode.rMax;
    } else {
        return checkIn(pos<<1|1, length);
    }
}

void pushUp(int pos) {
    TreeNode & leftNode = tree[pos<<1];
    TreeNode & rightNode = tree[pos<<1|1];
    TreeNode & curNode = tree[pos];

    curNode.lMax = leftNode.lMax;
    curNode.rMax = rightNode.rMax;

    if (leftNode.lMax == (leftNode.right - leftNode.left + 1)) {
        curNode.lMax = leftNode.Max + rightNode.lMax;
    }

    if (rightNode.rMax == (rightNode.right - rightNode.left + 1)) {
        curNode.rMax = leftNode.rMax + rightNode.Max;
    }

    curNode.Max = leftNode.Max > rightNode.Max ? leftNode.Max : rightNode.Max;
    int midSum = leftNode.rMax + rightNode.lMax;
    curNode.Max = curNode.Max > midSum ? curNode.Max : midSum;
}

void update(int L, int R, int c, int pos) {
    
    TreeNode & curNode = tree[pos];
    int left = curNode.left;
    int right = curNode.right;

    // if whole range is contained
    if (L <= left && right <= R) {
        curNode.lMax = curNode.Max = curNode.rMax = c ? 0 : (right - left + 1);
        curNode.color = c;
        return;
    }

    pushDown(pos);
    int mid = (left + right)>>1;
    if (R <= mid) { // has some part in left part
        update(L, R, c, pos<<1);
    } else if (L <= mid && R > mid) {
        update(L, mid, c, pos<<1);
        update(mid+1, R, c, pos<<1|1);
    } else if (L > mid) {
        update(L, R, c, pos<<1|1);
    }
    pushUp(pos);
}

int roomNum;
int checkTime;
int main() {
    scanf("%d %d", &roomNum, &checkTime);
    buildTree(1, 1, roomNum);
    for (int i = 1; i <= checkTime; i++) {
        int checkType;
        scanf("%d", &checkType);
        if (checkType == 1) {
            int length;
            scanf("%d", &length);
            if (tree[1].Max < length) {
                printf("0\n");
            } else {
                int pos = checkIn(1, length);
                printf("%d\n", pos);
                update(pos, pos + length - 1, 1, 1);
            }
        } else if (checkType == 2) {
            int begin, length;
            scanf("%d %d", &begin, &length);
            update(begin, begin + length - 1, 0, 1);
        }
    }
}

如果已经熟悉了线段树的区间更新的话,这道题也基本是道经典的基础应用题,但是如果没有系统的搞过线段树的区间更新并且悟性不高,那么还是挺坎坷的,比如我.....

一开始没想到用 记录从区间左起最多空位数 和 区间从右起最多空位数 以及 区间最多空位数这个trick, 其实这个trick之前解题就用过了,但是脑拙没想起,以后看到类似的这种区间连续元素的某种性质极值问题,都要考虑考虑这个trick。

但是就是知道了这个trick,也还是搞不出来,因为这道题要求用区间更新来做,我还是按照以前的更新到线段点来做,代码写到后头,自己都觉得不靠谱,无奈只得搜索,

才发现线段树区间更新这种题型,最开始搞线段树的时候看资料也看到过lazy标记,但是不懂,这次才算有了点认识,悲催的被各种附属中学的学生虐,并且还发现,之前自己做过的线段染色问题其实已经算是用到了区间更新的(不过貌似跟这类题也不完全一样),但是那时候似乎用的lazy标记过于局限题目本身,没有意识到。 网上对这道题的解释也都因为做题者本身会了区间更新,没有做出详细的讲解.

区间更新 其实是线段树的最大亮点和优势,对一个区间的一群数做了处理,并且只需一次,而不需要再细化到每个线段点,这样就极大的节省了时间,线段树的经典教程,主要就是胡浩的一个线段树完整版 和 zkw自己发明的自底至上线段树,不过这两个教材都不太面向新手,杨戈写的其实也是不错的,对新手比较照顾,有些重点:

<1>什么情况下,线段树可以发挥它应有的功能,回答我们的区间查询呢?当相邻的区间的信息,可以被合并成两个区间的并区间的信息时,就可以回答区间查询。

<2>我们在节点里增设一个域:标记。把对节点的修改情况储存在标记里面,这样,当我们自上而下地访问某节点时,就能把一路上所遇到的所有标记都考虑进去.

对于区间更新讲的还是比较清晰的: 即如果对于某个区间整个做出了某种修改,是不需要把这个修改完全执行到该区间的子区间(以及其下面所有的子区间)的,只需要在该区间加一个标记,标示该修改即可,这样就极大的节省了操作,而在真正的要进入到该区间分析操作时,才会根据此标记将对该区间做修改,同时还要把此标记下发到其子区间中,如果下一步修要对子区间进行分析操作,再根据标记修改子区间,并将标记分发到子区间的子区间即可,这个标记有一个形象的名字:lazy tag,除非是真正要用到这个区间,否则对这个区间的修改就只停留在这一层, 惰性更新。

例子:

区间 [1, 6] ,每个位置存放一个数,要求 在修改了区间的某些数以后,求出某个区间的最小值

那么线段树的结构是:

                            [1 6]

              [1 3]                       [4 6]

      [1 2]        [3 3]         [4 5]          [6 6]

[1 1]      [2 2]           [4 4]       [5 5]

每个区间都存放了该区间的最小值。

如果对[1,3]的所有数都 +1,那么如果不搞惰性tag, 就需要修改 [1 3]  [1 2] [3 3]  [1 1]  [2  2] [1 6] 这些区间,

但是如果给[1 3]加一个lazy tag标示[1 3] +1, 那么就只需修改[1 3]  [1 6], 即可,

然后这时候如果[4 6]都+1,那么同样操作,并且不会因为[1 3]的lazy tag出问题,

这时候,如果又对[1 2] +1, 那么在进入到[1 3],发现需要进一步进入其子区间时,就需要根据lazy tag将之前[1 3]+1的改动下发(pushdown)到子区间了,

及[1  3]的lazy tag被去掉(因为[1 3]当前没有被lazy的操作), 而[1 2] [3 3]则各自更新其lazy tag, 当然,[3 3]因为是一个线段点,已经不需要lazy tag了,

[1 2]这时候就有了两个被lazy的+1,这两个 lazyed +1 只有在进入[1 1]  [2 2]才会被真正执行,如果下一次是[1 3]或者其他区间更新,该区间的lazytag不会被影响,

得到的最小值也没有问题。

那么具体到这道题里面,每次有旅客批量的入住和离开,都是对一个连续区间的更新,只不过这里不再是+1,而是换成了标示某个区间的房间是否为空,

首先为了得到某个区间的最大连续空位数,需要每个区间维护三个信息:

<1>从该区间左起的最大连续空位数 lMax

<2>从该区间右起的最大连续空位数rMax

<3>该区间的最大连续空位数 Max

这样,就达成了线段树可以运作的条件:

当相邻的区间(子区间)的信息,可以被合并成两个区间的并区间(父区间)的信息时,就可以回答区间查询。

设一个区间 s, 其左右子区间 l, r, 那么 s .Max 就是  l.Max, r.Max 和 l.rMax + r.lMax的 最大值。

而  s.rMax 如果 r.rMax == r区间的长度(及r区间整个都是空的),那么就是 r.rMax + l.rMax, 否则就是 r.rMax.

s.lMax同理。

除了这些信息外,还需要维护一个lazy tag C, 如果C == -1, 那么表示此区间当前没有被lazy的操作,

如果C== 1, 那么表示此区间当前是被住满了的(当前区间应该被填满的操作)。

如果C==0, 那么表示此区间当前是空的(当前区间应该被清空的操作)。

注意上面的C不仅仅表示当前区间的状态(那是因为这道题的具体情况导致),也表示的是一种被lazy的操作(其子区间的信息还没有被这些操作更新)

首先,如果有客户入住的请求,要求连续K个房间,而题目又要求找最左边能满足需求的连续空房间,

case1: 如果 K > root区间的Max,那么说明当前根本不能满足旅客需求,建议其换旅馆,返回0

否则 case2: 如果 此区间的左子区间的Max>=K, 那么递归到左子区间。

否则 case3:如果左子区间.rMax + 右子区间.lMax >= K, 直接返回此组合区间的开始位置

否则case 4: 如果右子区间的Max>=K, 那么递归到右子区间.

这一步就是单纯的求出连续房间的起始位置。

胡浩在这一步会进行pushDown操作,不过感觉是不必要的,去掉也可以AC,也可能这一步是提前pushDown,没有啥坏处.

然后就是真正的更新旅客入住带来的影响了,将旅客要入住的区间作为参数,修改整个线段树,

case1: 如果整个区间都在此次入住的区间,那么就在整个区间加一个lazy tag即可,并且要更新lMax, Max 和 rMax(被住满了,都为0)

否则就要pushDown(pushDown里面判断lazy tag来决定是否真正pushDown,如果是-1,那么就没有被lazy的操作,直接return,否则根据lazy tag,更新左右子区间的各种信息,同时最后要把父区间的lazy tag设为-1,标示在此区间层面,已经没有被lazy的操作),pushdown以后,根据入住区间在当前区间的分布(都在左半边,左右半边都有,都在右半边)分别递归的调用update即可。

最后如果有旅客离开,那么同样进行上面的update,只不过操作从入住变成了离开, 影响lMax, rMax和Max的值.

还要注意的是,在对某个区间的update完成以后,其lMax, rMax , Max可能已经变化,因此,还需要一次pushUp更新其父区间所维护的信息.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值