线段树
线段树,是一种可以实现 \(O(logn)\) 区间修改、\(O(logn)\) 区间查询的算法。线段树存储的是某个区间中的值,利用二分查找的原理实现修改与查询。
已存储区间和为例,区间 \([1, 5]\) 可以分为两个小区间 \([1, 3]\) 和 \([4, 5]\),最后区间 \([1, 5]\) 的值就是这两个小区间的值的和。同样的,区间 \([l, r]\) 可以分为 \([l, mid]\) 和 \([mid + 1, r]\),其中 \(mid = \lfloor \frac{l + r}{2} \rfloor\)。就这样,我们可以利用这个数组快速查找区间和。
在建树、修改和查询中,我们始终都要维护好这棵树,因此我们需要一个 pushup 函数,来维护这棵树的正确性:
void pushup(int u)
{
w[u] = w[u * 2] + w[u * 2 + 1];
}
为了方便于计算,我们把编号为 \(u\) 的区间分为两个小区间时,把它们的编号分别设为 \(u \times 2, u \times 2 + 1\)。
接下来就是建树了。
我们在建立区间 \([l, r]\) 的树时,如果 \(l = r\),则说明这个区间内只有一个数 \(a_l\),因此我们直接把它赋值为 \(a_l\);如果 \(l \neq r\),则我们先计算出 \(mid\),然后分别建树 \([l, mid]\) 和 \([mid + 1, r]\),最后 pushup 一下,维护好这棵树。
void build(int u, int l, int r)
{
if (l == r)
{
w[u] = a[l];
return;
}
int mid = (l + r) >> 1;
build(u * 2, l, mid);
build(u * 2 + 1, mid + 1, r);
pushup(u);
}
接下来,考虑怎么进行区间修改。
用一个懒标记 \(lzy\) 数组来记录这个区间内被打上的标记。每一次修改和查询的时候,就先将这个区间和加上 \(lzy\) 的值乘上区间的长度。打标记和下传标记的代码如下:
void maketag(int u, int len, int x)
{
w[u] += len * x; //加了len个x
lzy[u] += x;
}
void pushdown(int u, int l, int r)
{
int mid = (l + r) >> 1;
maketag(u * 2, mid - l + 1, lzy[u]);
maketag(u * 2 + 1, r - mid, lzy[u]);
lzy[u] = 0; //懒标记清零
}
单点查询:
每次查询时,要保证当前区间内包含查找的数,也就是说,当你需要找第 \(p\) 个元素时,假设你当前找到了区间 \([l, r]\),则先算出 \(mid\),如果 \(p \leq mid\),说明 \(p\) 在它的左子树中,否则在它的右子树中。代码:
int query1(int u, int l, int r, int p)
{
if (l == r) return w[u]; //找到了
int mid = (l + r) >> 1;
pushdown(u, l, r); //先下传标记,不然以前的修改可能没有传下去
if (p <= mid) return query(u * 2, l, mid, p); //左子树
else return query(u * 2 + 1, mid + 1, r, p); //右子树
}
单点修改:基本同单点查询,但是修改后要 pushup。
void update1(int u, int l, int r, int p, int x)
{
if (l == r)
{
w[u] = x;
return;
}
int mid = (l + r) >> 1;
pushdown(u, l, r);
if (p <= mid)
{
update1(u * 2, l, mid, p, x);
}
else
{
update1(u * 2 + 1, mid + 1, r, p, x);
}
pushup(u);
}
区间查询与修改:在递归到 \([l, r]\) 的区间时,如果当前区间被包括在要查询的区间内,则直接返回区间值,如果完全无交集,返回 \(0\),如果有部分交集,则计算 \(mid\),再递归处理区间 \([l, mid]\) 和 \([mid + 1, r]\),返回它们的和。修改同理,若被包含,则全部打上标记,若没有交集,则不操作,否则递归处理。
bool inrange(int l, int r, int L, int R)
{
return (l <= L) && (R <= r);
}
bool outofrange(int l, int r, int L, int R)
{
return (r < L) || (R < l);
}
void update(int u, int lnow, int rnow, int l, int r, int k)
{
if (inrange(l, r, lnow, rnow))
{
maketag(u, rnow - lnow + 1, k);
}
else if (outofrange(l, r, lnow, rnow))
{
return;
}
else
{
int mid = (lnow + rnow) >> 1;
pushdown(u, lnow, rnow);
update(u * 2, lnow, mid, l, r, k);
update(u * 2 + 1, mid + 1, rnow, l, r, k);
pushup(u);
}
}
int query(int u, int lnow, int rnow, int l, int r)
{
if (inrange(l, r, lnow, rnow))
{
return w[u];
}
else if (outofrange(l, r, lnow, rnow))
{
return 0;
}
else
{
int mid = (lnow + rnow) >> 1;
pushdown(u, lnow, rnow);
return query(u * 2, lnow, mid, l, r) + query(u * 2 + 1, mid + 1, rnow, l, r);
}
}

浙公网安备 33010602011771号