Hotel
线段树:区间合并。
首次写区间合并,开始的时候没有想明白怎么做。看了别人的解题报告,终于明白了操作的各种过程。
其实与前面的线段树题目没什么太大的区别。主要是在线段树节点保存的值上多增加了一些。
首先看节点域:
struct Tree{
int l , r , lans , rans , mans , f ;
}
一共五个变量,l,r的含义与之前相同,为该区间的范围。lans表示的是该区间左端点起连续的空房间的个数,对应的rans为该区间右端点起连续的空房间的个数,mans为该区间最长的空房间的个数,f为lazy标记用。
对于查询操作1 x,如果查询到以元节点,那么这个点肯定是查询的结果,返回,对于非元节点意外的节点,我们都需要将该节点的信息更新到子树中。然后我们看当前节点c的左子树上是不是有足够的空房间,这里就是判断左子树上最长区间的长度是不是大于等于给定的房间个数x,如果有显然我们应该到左子树上继续查询;然后我们看中间连续的区间是不是存在(因为我们将区间划分成左子树和右子树的时候,将连续的区间分开了),所以判断左子树右连续(也就是tree[L(c)].rans)区间个数与右子树左连续(也就是tree[R(c)].lans)之和是不是有足够的房间,如果有显然可以直接计算出所要查询的房间的最小编号:tree[L(c)].r - tree[L(c)].rans + 1 .如果还没有那么,显然我们应该看右子树中最长的区间的区间长度。
那么在看更新操作:就是将某一段赋值为0或者为1,这里我用0表示该区间是可用的,1表示该区间不可用。如果需要修改的区间与节点表示的区间相等,就是直接更新该区间的值了。如果不相等,和一般应用的线段树一样。由于使用了lazy标记,那么我们就需要将信息向下传递,使用pushDown()函数。然后,看区间是在左子树,还是在右子树,或者两个区间都有。紧接着,我们需要将子树的信息更新到父节点。对于父节点的lans ,rans都比较好更新,而mans的可能取值,肯定是在左子树的mans,右子树的mans,以及区间的交叉和:tree[L(c)].rans + tree[R(c)].lans中。
/*
author : csuchenan
LANG : c++
PROG : POJ3667 hotel
Algorithm : Segment Tree
*/
#include <cstdio>
#include <cstring>
#define maxn 50005
#define L(x) x<<1
#define R(x) x<<1|1
#define M(x , y) (x + y)>>1
#define Max(x , y) x > y ? x : y
#define Min(x , y) x < y ? x : y
struct Tree{
int l ;
int r ;
int f ;
//int c ;//是否被覆盖,0 代表未覆盖,1,已经覆盖,-1混合区间
int lans ;//从该区间左端点起的最长的序列
int rans ;//从该区间右端点起的最长的序列
int mans ;//该区间最长的序列
}tree[maxn * 3] ;
void build(int l , int r , int c){
tree[c].l = l ;
tree[c].r = r ;
//tree[c].c = 0 ;
tree[c].f = -1 ;
tree[c].lans = r - l + 1 ;
tree[c].rans = r - l + 1 ;
tree[c].mans = r - l + 1 ;
if(l == r)
return ;
int m = M(l , r) ;
build(l , m , L(c)) ;
build(m + 1 , r , R(c)) ;
}
void pushDown(int c){
int l = L(c) ;
int r = R(c) ;
if(tree[c].f >= 0){
tree[l].f = tree[c].f ;
tree[r].f = tree[c].f ;
//tree[l].c = tree[l].f ;
//tree[r].c = tree[r].f ;
if(tree[c].f){
tree[l].lans = 0 ;
tree[l].rans = 0 ;
tree[l].mans = 0 ;
tree[r].lans = 0 ;
tree[r].rans = 0 ;
tree[r].mans = 0 ;
}
else{
tree[l].lans = tree[l].r - tree[l].l + 1 ;
tree[l].rans = tree[l].r - tree[l].l + 1 ;
tree[l].mans = tree[l].r - tree[l].l + 1 ;
tree[r].lans = tree[r].r - tree[r].l + 1 ;
tree[r].rans = tree[r].r - tree[r].l + 1 ;
tree[r].mans = tree[r].r - tree[r].l + 1 ;
}
tree[c].f = -1 ;
}
}
void pushUp(int c){
int l = L(c) ;
int r = R(c) ;
tree[c].mans = Max(tree[l].mans , (Max(tree[r].mans , tree[r].lans + tree[l].rans)) ) ;
tree[c].lans = tree[l].lans ;
tree[c].rans = tree[r].rans ;
if(tree[l].lans == tree[l].r - tree[l].l + 1){
tree[c].lans += tree[r].lans ;
}
if(tree[r].rans == tree[r].r - tree[r].l + 1){
tree[c].rans += tree[l].rans ;
}
//if(tree[l].c != tree[r].c)
// tree[c].c = -1 ;
//else
// tree[c].c = tree[l].c ;
}
void update(int op , int x , int y , int c){
if(tree[c].l == x && tree[c].r == y){
//tree[c].c = op ;
tree[c].f = op ;
//如果被覆盖为1
if(op){
tree[c].lans = 0 ;
tree[c].rans = 0 ;
tree[c].mans = 0 ;
}
else{
tree[c].lans = tree[c].r - tree[c].l + 1 ;
tree[c].rans = tree[c].r - tree[c].l + 1 ;
tree[c].mans = tree[c].r - tree[c].l + 1 ;
}
return ;
}
pushDown(c) ;
int m = M(tree[c].l , tree[c].r) ;
if(y <= m){
update(op , x , y , L(c)) ;
}
else if(x > m){
update(op , x , y , R(c)) ;
}
else{
update(op , x , m , L(c)) ;
update(op , m + 1 , y , R(c)) ;
}
pushUp(c);
}
int query(int x , int c){
if(tree[c].l == tree[c].r)
return tree[c].l ;
pushDown(c) ;
if(tree[L(c)].mans >= x){
return query(x , L(c)) ;
}
else if(tree[L(c)].rans + tree[R(c)].lans >= x ){
return tree[L(c)].r - tree[L(c)].rans + 1 ;
}
else if(tree[R(c)].mans >= x){
return query(x , R(c)) ;
}
return 0 ;
}
int main(){
int n ;
int m ;
int x ;
int y ;
int op ;
scanf("%d%d" , &n ,&m) ;
build(1 , n , 1) ;
for(int k(0) ; k < m ; k ++){
scanf("%d" , &op) ;
if(op == 1){
scanf("%d" , &x) ;
int room = query(x , 1) ;
printf("%d\n" , room) ;
if(room)
update(1 , room , room + x - 1 , 1) ;
}
else{
scanf("%d%d" , &x , &y) ;
update(0 , x , x + y - 1 , 1) ;
}
}
return 0 ;
}

本文介绍了一种线段树的应用——区间合并,并通过具体实现案例进行详细解析。重点介绍了线段树节点的设计,包括左端连续空房间、右端连续空房间、最长连续空房间等关键概念。
687

被折叠的 条评论
为什么被折叠?



