// 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更新其父区间所维护的信息.