线段树的修改
一.单点修改
单点修改就很简单啦,直接递归到叶子节点修改就行
void modify(int k, int l, int r, int x, int v)
{
if(l == r && l == x)
{
tree[l] = x;
return;
}
int mid = (l + r ) >> 1;
if(x <= mid)
modify(ls(k), l, mid, x, v);
else
modify(rs(k), mid + 1, r ,x ,v);
tree[k] = tree[ls(k)] + tree[rs(k)];
return;
}
二.区间修改
有时候我们不止解决单点修改,单点询问,还要解决区间修改,区间询问。
我们如果把一个区间拆分成单点修改最坏情况下,复杂度可以达到\(O(mn\log n)\)还不如朴素算法。
于是,我们引入一种新的方法——延迟标记
1.Lazy_tag(懒标记)
我们在每个节点上维护一个值\(tag[k]\)表示该节点所辖区间里所有的数的值都为\(tag[k]\)同时,我们改变节点的值\(tree[k]\)为\(tag[k] * (l + r - 1)\)。
但是,如果我们不需要继续向下递归,我们就不对下面的子节点进行修改。
例如, 我们将下图中\([2,9]\)的数字都改成\(v\)。就给绿色节点打上标记,表示区间内的数字都为已经改为\(v\)。且区间和为\((l + r - 1) * v\)
2.标记下传
但是, 如果我们要查询他子节点的值该如何呢?
首先, 我们只改变了\(k\)的值。但是,如果我们不继续向下递归,我们\(k\)的孩子节点就不会被改变。
换句话说,在这里,\(k\)的值是修改后的值,但是\(k\)的子节点是没有修改的,但是,因为我们只需要\(k\)的值,所以我们不需要对其孩子的值进行修改。
在我们需要查询其孩子的值时再将\(k\)的标记下传到他的左右儿子,并将其上的标记清零。
每次向下递归都要下传标记!!!,不管你是单点还是区间,查询还是修改,都要下传!因为你要将子节点的值改为正确值才能再次操作。
这里以区间修改为例。
void add(ll k, ll l, ll r, ll v)//改标记
{
tag[k] += v;
tree[k] += (r - l + 1) * v;
}
void push_down(ll k, ll l, ll r, ll mid)//标记下传
{
if(!tag[k])
return;
add(ls(k), l, mid, tag[k]);
add(rs(k), mid + 1, r, tag[k]);
tag[k] = 0;
}
void change(ll k, ll l, ll r, ll x, ll y, ll v)//区间修改
{
if(x <= l && r <= y)
{
add(k, l, r, v);
return;
}
ll mid = (l + r) >> 1;
push_down(k, l, r, mid);//每次递归都要下传!!!
if(x <= mid)
change(ls(k), l, mid, x, y, v);
if(y > mid)
change(rs(k), mid + 1, r, x, y, v);
tree[k] = tree[ls(k)] + tree[rs(k)];
}
因为我暂时不会标记永久化所以这部分内容之后会补齐。
完结撒花!!!