思路简述
看到题目中有着区间修改,区间查询的要求,不难想到此题可以利用线段树通过。
首先,对于一个区间的合法性,反转后并不会改变;同时我们注意到,一个区间的合法性,只与它的两个子区间的合法性,以及左儿子区间最右边的值是否等于右儿子区间最左边的值有关。不难想到,我们的线段树上要维护的是这个区间是否合法,区间最左边的元素的值以及区间最右边的元素的值。
代码如下:
struct node {
// 左端点,右端点,最左边元素的值,最右边元素的值,懒标记
int l, r, lv, rv, tag;
bool islegal; //区间是否合法
} tree[N << 2];
那么,我们不难写出 pushup
函数:
inline void pushup(int p) {
tree[p].lv = tree[ls].lv, tree[p].rv = tree[rs].rv;
// 要求为两个子区间都合法,以及左儿子区间最右边的值不等于右儿子区间最左边的值
if (tree[ls].islegal && tree[rs].islegal && (tree[ls].rv != tree[rs].lv)) tree[p].islegal = true;
else tree[p].islegal = false;
return ;
}
如果我们需要更新一个区间,我们只需要修改三个值:最左边元素的值,最右边元素的值,懒标记。那么,我们不难想到下放一个懒标记,也只需要更改这三个值即可。那么我们可以写出这样的 pushdown
函数以及 update
函数。
inline void pushdown(int p) {
if (tree[p].tag) {
tree[ls].tag ^= 1, tree[rs].tag ^= 1; // 下放标记
tree[ls].lv ^= 1, tree[ls].rv ^= 1; // 更改区间左右端点元素值
tree[rs].lv ^= 1, tree[rs].rv ^= 1;
tree[p].tag = 0; // 清空懒标记
}
return ;
}
void update(int p, int l, int r) {
if (tree[p].l >= l && tree[p].r <= r) {
tree[p].tag ^= 1;
tree[p].lv ^= 1, tree[p].rv ^= 1;
return ;
}
pushdown(p);
int mid = (tree[p].l + tree[p].r) >> 1;
if (l <= mid) update(ls, l, r);
if (r > mid) update(rs, l, r);
pushup(p);
return ;
}
本题的查询方式比较复杂,不能通过两个子区间是否合法来判断区间是否合法,我们不妨直接返回一段区间,合并后判断这个区间的合法性。代码如下:
node query(int p, int l, int r) {
if (tree[p].l >= l && tree[p].r <= r) return tree[p];
pushdown(p);
int mid = (tree[p].l + tree[p].r) >> 1;
if (r <= mid) return query(ls, l, r); // 查询区间在左儿子下
else if (l > mid) return query(rs, l, r); // 查询区间在右儿子下
else { // 需要两端区间合并
node queryl = query(ls, l, r), queryr = query(rs, l, r);
node tmp;
tmp.lv = queryl.lv, tmp.rv = queryr.rv;
tmp.islegal = (queryl.islegal && queryr.islegal && (queryl.rv != queryr.lv)); // 类似于pushup函数的更新
return tmp;
}
}
这样的话,我们就能以一种简单的方式解决这个问题。