您的位置:首页 > 其它

【SJTUOJ笔记】P1122 二哥开房间

2018-04-01 17:10 148 查看
https://acm.sjtu.edu.cn/OnlineJudge/problem/1122

Hint已经告诉我们,这是一道线段树题。其实,此题确实展示了线段树的基本功能之一:维护01序列中区间最大连续0的长度。

最普通的线段树结点中有
l
r
两个成员,表示该结点对应区间[l,r][l,r]。添加不同的成员可以维护不同的属性。在本题中,我们要维护01序列中最大连续0的长度,那么需要添加:

lsum
,表示以
l
为起点的连续0的最大长度;

rsum
,表示以
r
为终点的连续0的最大长度;

sum
,表示区间[l,r][l,r]中连续0的最大长度;

lazy
,懒惰标记。
lazy=0
表示无标记,
lazy=1
表示区间被占用,
lazy=2
表示区间被回收。

接下来考虑如何维护这几个成员。显然对于叶子节点,几个sum属性要么全为0,要么全为1。而对于非叶子节点,我们用
pushUp()
函数来更新。

void pushUp(int x){ //x是要更新的结点编号
//置为初始值
a[x].lsum = a[x << 1].lsum; //左连续零等于左儿子的左连续零
a[x].rsum = a[x << 1 | 1].rsum; //右连续零等于右儿子的右连续零
int mid = (a[x].l + a[x].r) >> 1;
if (a[x << 1].lsum == mid - a[x].l + 1) //左儿子全是零,那么左连续零应该再加上右儿子的左连续零
a[x].lsum += a[x << 1 | 1].lsum;
if (a[x << 1 | 1].rsum == a[x].r - mid) //同理
a[x].rsum += a[x << 1].rsum;
a[x].sum = max(a[x << 1].rsum + a[x << 1 | 1].lsum, max(a[x << 1].sum, a[x << 1 | 1].sum));
//根据最大连续零出现的位置有三种可能
//以上三项依次为:跨左右儿子,仅在左儿子,仅在右儿子
//跨左右儿子的情况,必定是左儿子的右连续零加上右儿子的左连续零
}


然后是询问操作。根结点的
sum
表示的是整个区间的最大连续零,先判断有没有可能分配。能分配,再从根开始往下二分,每次尽可能向左儿子移动。

int query(int x, int len){
if (a[x].l == a[x].r)
return a[x].l;
pushDown(x); //先下推lazy标记
int mid = (a[x].l + a[x].r) >> 1;
if (a[x << 1].sum >= len) //左儿子可以分配,必定向左走
return query(x << 1, len);
else if (a[x << 1].rsum + a[x << 1 | 1].lsum >= len) //中间可分配
return mid - a[x << 1].rsum + 1;
else //只能向右分配
return query(x << 1 | 1, len);
}


在分配和释放一段序列后,需要打上
lazy
标记。
lazy
标记的维护方法和其他种类的标记相同。
lazy=1
对应的操作是把
lsum
rsum
sum
全部置0;而
lazy=2
对应的操作是吧
lsum
rsum
sum
全部置为区间长度。具体细节不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: