题目链接:传送门
题目描述:
参考样例,第一行输入n(1 ≤ n ≤ 50,000),m(1 ≤ M < 50,000) ,n代表有n个房间,编号为1—n,开始都为空房,m表示以下有m行操作,以下 每行先输入一个数 i ,表示一种操作:
若i为1,表示查询房间,再输入一个数x,表示在1–n 房间中找到长度为x的连续空房,输出连续x个房间中左端的房间号,尽量让这个房间号最小,若找不到长度为x的连续空房,输出0。
若i为2,表示退房,再输入两个数 x,y 代表 房间号 x—x+y-1 退房,即让房间为空。
输入样例
10 6
1 3
1 3
1 3
1 3
2 5 5
1 6
输出样例
1
4
7
0
5
这算是对线段树的理解的考察吧
我们考虑维护
我们维护区间长度()
左右端点()
一个标记表示是开房还是退房()
这个区间内的最长0的个数()
这个区间从左端点开始最长0的个数()
这个区间从右端点开始最长0的个数()
在下传标记的时候就可以维护了
具体怎么样维护在代码里说
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <complex>
#include <algorithm>
#include <climits>
#include <queue>
#include <map>
#include <vector>
#include <iomanip>
#define
#define
#define
using namespace std;
struct node {
int l, r, w, lt, rt, t, f; //各个维护的值
}tree[A];
int n, m, x, y, typ, ans;
void up(int k) {
tree[k].t = max(tree[k << 1].rt + tree[k << 1 | 1].lt, max(tree[k << 1].t, tree[k << 1 | 1].t));
//当前区间最长的一段可以是左儿子的右边开始最长区间长度加上左儿子的左边开始最长区间长度
//还可以是左儿子的区间最长长度或右儿子的区间最长长度
if (tree[k << 1].lt == tree[k << 1].w) tree[k].lt = tree[k << 1].lt + tree[k << 1 | 1].lt;
//可以是左儿子的区间长度加上右儿子的左边开始最长区间长度
else tree[k].lt = tree[k << 1].lt;
if (tree[k << 1 | 1].rt == tree[k << 1 | 1].w) tree[k].rt = tree[k << 1].rt + tree[k << 1 | 1].rt;
//可以是右儿子的区间长度加上左儿子的右边开始最长区间长度
//想一想,这两块可以并到一起
else tree[k].rt = tree[k << 1 | 1].rt;
}
void build(int k, int l, int r) {
tree[k].l = l; tree[k].r = r; tree[k].w = r - l + 1; //w维护这段区间的长度
if (l == r) {
tree[k].lt = tree[k].rt = tree[k].t = 1; //初值当然都为1
return;
}
int m = (l + r) >> 1;
build(k << 1, l, m);
build(k << 1 | 1, m + 1, r);
up(k);
}
void down(int k) { //标记下传
if (tree[k].f == 1) {
tree[k << 1].lt = tree[k << 1].rt = tree[k << 1].t = 0; tree[k << 1].f = 1;
tree[k << 1 | 1].lt = tree[k << 1 | 1].rt = tree[k << 1 | 1].t = 0; tree[k << 1 | 1].f = 1; tree[k].f = 0;
//这是删除点,也就是退房
}
if (tree[k].f == 2) {
tree[k << 1].lt = tree[k << 1].rt = tree[k << 1].t = tree[k << 1].w; tree[k << 1].f = 2;
tree[k << 1 | 1].lt = tree[k << 1 | 1].rt = tree[k << 1 | 1].t = tree[k << 1 | 1].w; tree[k << 1 | 1].f = 2; tree[k].f = 0;
//开房
}
}
void change(int k, int l, int r, int typ) {
if (l <= tree[k].l and r >= tree[k].r) {
if (typ == 1) {
tree[k].lt = tree[k].rt = tree[k].t = 0;
tree[k].f = 1;
//退房
}
else tree[k].lt = tree[k].rt = tree[k].t = tree[k].w, tree[k].f = 2;//开房
return;
}
down(k);
int m = (tree[k].l + tree[k].r) >> 1;
if (l <= m) change(k << 1, l, r, typ);
if (r > m) change(k << 1 | 1, l, r, typ);
up(k);
}
void ask(int k) {
//可能大多数人会觉得这里有判断是否在区间内的操作
//但这里的操作区间是1-n,所以就没有必要判断了
//直接下放标记
down(k);
if (tree[k << 1].t >= x) ask(k << 1); //向左儿子找
else if (tree[k << 1].rt + tree[k << 1 | 1].lt >= x) ans = tree[k << 1].r - tree[k << 1].rt + 1;
//两端区间并起来能达到题目要求
//也就是明确了区间端点
//到这里就可以返回答案了
//就按题目要求返回左端点的编号
else if (tree[k << 1 | 1].t >= x) ask(k << 1 | 1); //向右儿子找
up(k);
}
int main() {
scanf("%d%d", &n, &m);
build(1, 1, n);
while (m--) {
scanf("%d", &typ);
if (typ == 1) {
scanf("%d", &x);
if (tree[1].t < x) puts("0");
else {
ans = 0;
ask(1); //习惯写不带返回值的函数
printf("%d\n", ans);
change(1, ans, ans + x - 1, 1); //要求查完之后把房开开
}
}
else {
scanf("%d%d", &x, &y);
change(1, x, x + y - 1, 2); //退房
}
}
return 0;
}