PKU 3667 Hotel
题目链接: http://poj.org/problem?id=3667
/**/
/*
题意:
给出M(M <= 50000)组操作,每组操作的形式如下:
1 D: 询问是否有长度为D的连续区间,如果存在输出最左边的,否则输出0
,并且将这块连续的区间占据。
2 X D:将从X开始的连续D块空间释放掉。
解法:
线段树
思路:
线段树染色问题,和ZJU 2301 Color the Ball类似,也是寻找最长连
续区间。线段树中可以保存如下信息:
enum eKind{
EK_MULTIPLE = -1,
EK_EMPTY = 0, // 清空当前空间
EK_FULL = 1, // 填充当前空间
};
int root, l, r;
eKind cover; // 当前区间的种类的枚举
int lMax; // 包含左区间的连续空闲区间的最大值
int rMax; // 包含右区间的连续空闲区间的最大值
int mMax; // 当前结点管辖区间的最大值
首先来看下问题对应的操作,查询连续D区间这个我在后面会详细介绍
,先来看看插入操作,题目中有两种插入,一个是插入一块满的区间,另一
个是删除一段固定长度的区间,其实原理是一样的,我们只要用一个lazy标
记即可。我的结构中的lazy标记用eKind这个枚举类型来表示。EK_EMPTY表示
清空一段区间,EK_FULL表示填充一段区间。每次插入操作只进行到当前区间
完全覆盖结点区间时。如果完全覆盖,则根据插入的eKind类型填充mMax、
lMax、rMax的信息,否则将当前结点有的lazy标记传递给两个子结点,更新
他们的结点信息,然后递归左右儿子,继续插入操作,递归返回时我们用以
下函数从左右儿子中得到当前结点的信息:
void UpdateBy(Tree* ls, Tree* rs);
之所以把它写成函数是因为这里的处理比较麻烦,很容易出错,并且需要
调用多次,这个函数的作用就是通过左右儿子的信息填充本身的信息。
信息一多,处理的时候就要极为小心,因为很容易出错。
lMax表示当前结点的包含左闭区间的最优解。
rMax表示当前结点的包含右闭区间的最优解。
mMax则是当前区间的最优解。
这样我们就可以通过传递性在儿子结点的属性都得知的情况下将父亲的值
计算出来,最后递归到根结点。具体的计算过程可以自己画棵树看一下,
然后是查询操作,查询的话首先判断当前结点的最大值是否比给定的查
询值小,如果是这样直接返回0表示没有找到。否则将当前值和左儿子的最大
值进行比较,如果满足给定值小于等于左儿子的最大值则递归计算左儿子,
如果不是,则比较的不是右儿子,因为有可能这个最大空闲区间是在左儿子的
rMax + 右儿子的 lMax 上,因此需要和这个值比较,最后才是和右儿子的值
比较,这里可以保证肯定能找到一个解,需要注意的是在询问的时候需要将当
前结点的lazy标记往下传。
*/
#include < iostream >
using namespace std;
#define maxn 50010
enum eKind {
EK_MULTIPLE = -1,
EK_EMPTY = 0, // 清空当前空间
EK_FULL = 1, // 填充当前空间
} ;
struct Tree {
int root, l, r;
eKind cover;
int mMax, lMax, rMax;
int len() {
return r - l + 1;
}
void CoverBy(eKind val);
void UpdateBy(Tree* ls, Tree* rs);
void TranslateToSon();
void TranslateTo(Tree* ts);
} T[maxn * 4 ];
int MMax( int a, int b) {
return a > b ? a : b;
}
int MMax( int a, int b, int c, int d) {
return MMax(MMax(a, b), MMax(c, d));
}
void Tree::TranslateToSon() {
if(cover != EK_MULTIPLE) {
TranslateTo(&T[root<<1]);
TranslateTo(&T[root<<1|1]);
}
cover = EK_MULTIPLE;
}
void Tree::TranslateTo(Tree * ts) {
ts->CoverBy(cover);
}
void Tree::CoverBy(eKind val) {
cover = val;
if(val == EK_EMPTY) {
mMax = lMax = rMax = len();
}else if(val == EK_FULL) {
mMax = lMax = rMax = 0;
}
}
void Tree::UpdateBy(Tree * ls, Tree * rs) {
lMax = ls->lMax; if(lMax == ls->len()) lMax += rs->lMax;
rMax = rs->rMax; if(rMax == rs->len()) rMax += ls->rMax;
mMax = MMax(ls->mMax, rs->mMax);
mMax = MMax(mMax, ls->rMax + rs->lMax, lMax, rMax);
}
void Build( int root, int l, int r) {
T[root].root = root;
T[root].l = l;
T[root].r = r;
T[root].cover = EK_EMPTY;
T[root].mMax = T[root].lMax = T[root].rMax = T[root].len();
if(l == r) {
return ;
}
int mid = (l + r) >> 1;
Build(root<<1, l, mid);
Build(root<<1|1, mid+1, r);
}
void Insert( int root, int l, int r, eKind val) {
if(l > T[root].r || r < T[root].l)
return ;
if(l <= T[root].l && T[root].r <= r) {
T[root].CoverBy(val);
return ;
}
T[root].TranslateToSon();
Insert(root<<1, l, r, val);
Insert(root<<1|1, l, r, val);
T[root].UpdateBy(&T[root<<1], &T[root<<1|1]);
}
int Query( int root, int val) {
// 当前结点的最大连续块小于要求的块
if(val > T[root].mMax) {
return 0;
}
// 递归结束到元区间位置
if(T[root].l == T[root].r) {
if(val == 1) {
return T[root].l;
}
return 0;
}
T[root].TranslateToSon();
if(val <= T[root<<1].mMax) {
// 在左子树中能找到最大块
return Query(root<<1, val);
}else if(val <= T[root<<1].rMax + T[root<<1|1].lMax) {
// 在左右子树的合并块中找到
return (T[root<<1].r - T[root<<1].rMax + 1);
}else {
// 在右子树中能找到最大块
return Query(root<<1|1, val);
}
}
int n, m;
int main() {
int i;
while(scanf("%d %d", &n, &m) != EOF) {
Build(1, 1, n);
for(i = 0; i < m; i++) {
int op, x, y;
scanf("%d", &op);
if(op == 1) {
scanf("%d", &x);
int nPos = Query(1, x);
if(nPos) {
Insert(1, nPos, nPos + x - 1, EK_FULL);
}
printf("%d\n", nPos);
}else {
scanf("%d %d", &x, &y);
Insert(1, x, x + y - 1, EK_EMPTY);
}
}
}
return 0;
}
题意:
给出M(M <= 50000)组操作,每组操作的形式如下:
1 D: 询问是否有长度为D的连续区间,如果存在输出最左边的,否则输出0
,并且将这块连续的区间占据。
2 X D:将从X开始的连续D块空间释放掉。
解法:
线段树
思路:
线段树染色问题,和ZJU 2301 Color the Ball类似,也是寻找最长连
续区间。线段树中可以保存如下信息:
enum eKind{
EK_MULTIPLE = -1,
EK_EMPTY = 0, // 清空当前空间
EK_FULL = 1, // 填充当前空间
};
int root, l, r;
eKind cover; // 当前区间的种类的枚举
int lMax; // 包含左区间的连续空闲区间的最大值
int rMax; // 包含右区间的连续空闲区间的最大值
int mMax; // 当前结点管辖区间的最大值
首先来看下问题对应的操作,查询连续D区间这个我在后面会详细介绍
,先来看看插入操作,题目中有两种插入,一个是插入一块满的区间,另一
个是删除一段固定长度的区间,其实原理是一样的,我们只要用一个lazy标
记即可。我的结构中的lazy标记用eKind这个枚举类型来表示。EK_EMPTY表示
清空一段区间,EK_FULL表示填充一段区间。每次插入操作只进行到当前区间
完全覆盖结点区间时。如果完全覆盖,则根据插入的eKind类型填充mMax、
lMax、rMax的信息,否则将当前结点有的lazy标记传递给两个子结点,更新
他们的结点信息,然后递归左右儿子,继续插入操作,递归返回时我们用以
下函数从左右儿子中得到当前结点的信息:
void UpdateBy(Tree* ls, Tree* rs);
之所以把它写成函数是因为这里的处理比较麻烦,很容易出错,并且需要
调用多次,这个函数的作用就是通过左右儿子的信息填充本身的信息。
信息一多,处理的时候就要极为小心,因为很容易出错。
lMax表示当前结点的包含左闭区间的最优解。
rMax表示当前结点的包含右闭区间的最优解。
mMax则是当前区间的最优解。
这样我们就可以通过传递性在儿子结点的属性都得知的情况下将父亲的值
计算出来,最后递归到根结点。具体的计算过程可以自己画棵树看一下,
然后是查询操作,查询的话首先判断当前结点的最大值是否比给定的查
询值小,如果是这样直接返回0表示没有找到。否则将当前值和左儿子的最大
值进行比较,如果满足给定值小于等于左儿子的最大值则递归计算左儿子,
如果不是,则比较的不是右儿子,因为有可能这个最大空闲区间是在左儿子的
rMax + 右儿子的 lMax 上,因此需要和这个值比较,最后才是和右儿子的值
比较,这里可以保证肯定能找到一个解,需要注意的是在询问的时候需要将当
前结点的lazy标记往下传。
*/
#include < iostream >
using namespace std;
#define maxn 50010
enum eKind {
EK_MULTIPLE = -1,
EK_EMPTY = 0, // 清空当前空间
EK_FULL = 1, // 填充当前空间
} ;
struct Tree {
int root, l, r;
eKind cover;
int mMax, lMax, rMax;
int len() {
return r - l + 1;
}
void CoverBy(eKind val);
void UpdateBy(Tree* ls, Tree* rs);
void TranslateToSon();
void TranslateTo(Tree* ts);
} T[maxn * 4 ];
int MMax( int a, int b) {
return a > b ? a : b;
}
int MMax( int a, int b, int c, int d) {
return MMax(MMax(a, b), MMax(c, d));
}
void Tree::TranslateToSon() {
if(cover != EK_MULTIPLE) {
TranslateTo(&T[root<<1]);
TranslateTo(&T[root<<1|1]);
}
cover = EK_MULTIPLE;
}
void Tree::TranslateTo(Tree * ts) {
ts->CoverBy(cover);
}
void Tree::CoverBy(eKind val) {
cover = val;
if(val == EK_EMPTY) {
mMax = lMax = rMax = len();
}else if(val == EK_FULL) {
mMax = lMax = rMax = 0;
}
}
void Tree::UpdateBy(Tree * ls, Tree * rs) {
lMax = ls->lMax; if(lMax == ls->len()) lMax += rs->lMax;
rMax = rs->rMax; if(rMax == rs->len()) rMax += ls->rMax;
mMax = MMax(ls->mMax, rs->mMax);
mMax = MMax(mMax, ls->rMax + rs->lMax, lMax, rMax);
}
void Build( int root, int l, int r) {
T[root].root = root;
T[root].l = l;
T[root].r = r;
T[root].cover = EK_EMPTY;
T[root].mMax = T[root].lMax = T[root].rMax = T[root].len();
if(l == r) {
return ;
}
int mid = (l + r) >> 1;
Build(root<<1, l, mid);
Build(root<<1|1, mid+1, r);
}
void Insert( int root, int l, int r, eKind val) {
if(l > T[root].r || r < T[root].l)
return ;
if(l <= T[root].l && T[root].r <= r) {
T[root].CoverBy(val);
return ;
}
T[root].TranslateToSon();
Insert(root<<1, l, r, val);
Insert(root<<1|1, l, r, val);
T[root].UpdateBy(&T[root<<1], &T[root<<1|1]);
}
int Query( int root, int val) {
// 当前结点的最大连续块小于要求的块
if(val > T[root].mMax) {
return 0;
}
// 递归结束到元区间位置
if(T[root].l == T[root].r) {
if(val == 1) {
return T[root].l;
}
return 0;
}
T[root].TranslateToSon();
if(val <= T[root<<1].mMax) {
// 在左子树中能找到最大块
return Query(root<<1, val);
}else if(val <= T[root<<1].rMax + T[root<<1|1].lMax) {
// 在左右子树的合并块中找到
return (T[root<<1].r - T[root<<1].rMax + 1);
}else {
// 在右子树中能找到最大块
return Query(root<<1|1, val);
}
}
int n, m;
int main() {
int i;
while(scanf("%d %d", &n, &m) != EOF) {
Build(1, 1, n);
for(i = 0; i < m; i++) {
int op, x, y;
scanf("%d", &op);
if(op == 1) {
scanf("%d", &x);
int nPos = Query(1, x);
if(nPos) {
Insert(1, nPos, nPos + x - 1, EK_FULL);
}
printf("%d\n", nPos);
}else {
scanf("%d %d", &x, &y);
Insert(1, x, x + y - 1, EK_EMPTY);
}
}
}
return 0;
}