之前我们讲过线段树的单点修改和区间查询,现在我们来谈一下区间修改和区间查询。
这里是上一篇博客链接:浅谈线段树2
区间修改
假如我们对区间[L, R]内的a[i]全部进行+x的操作,这里我们就需要进行区间修改的操作,话不多说先看图:
定义一个数组lazytag[4*N],这个数组存的是每一个子树序点所对应的区间修改的修改量。
void spread(int node, int l, int r) {
if (lazytag[node]) {
lazytag[lson] += lazytag[node];
lazytag[rson] += lazytag[node];
int mid = (l + r) / 2;
sum[lson] += 1LL * lazytag[node] * (mid - l + 1);
sum[rson] += 1LL * lazytag[node] * (r - mid);
lazytag[node] = 0;
}
}
spread函数中,参数node表示树的节点序,l和r分别表示node区间代表的区间[l, r]。这个函数的操作是什么呢?
如果lazytag[node]不为0,那么就可以将该区间二分,下放需要加的x,同时改变左子树和右子树的sum。左子树的改变区间为[l, mid],即它需要加上(mid - l + 1)个x,同理右子树相同。但是下放x后,需要把lazytag[node]置0。
提出问题:我们已知使用懒标记能使区间修改的操作更快,那么它快在哪呢?
回答问题:这里的区间修改操作是一个对区间整体的改变,没有下放到单点!如果直接对线性的数组进行区间修改,那么需要的操作应该是(r - l + 1)次,然而如果用线段树加懒标记维护一下那么只需要log(r - l + 1)次操作。
补充:如果想使修改量下放到单点,那么可以进行一次单点查询和懒标记下放操作,代码如下
//单点查询
ll query(int node, int l, int r, int idx) {
spread(node, l, r);
//区间修改的懒标记不是完全下放
//它是区间下放
if (l == r) {
return sum[node];
}
//spread(node, l, r);
else {
int mid = (l + r) / 2;
if (idx <= mid)
return query(lson, l, mid, idx);
else
return query(rson, mid + 1, r, idx);
}
在每次开始查询前都要将子树序点为node处的懒标记具体下放至lson和rson。
回归正题,知道懒标记这一方法后,我们来看看区间修改的操作:
//change
void change(int node, int l, int r, int L, int R, int x) {
if (l == L and r == R) {
lazytag[node] += x;
sum[node] += 1ll*x*(r - l + 1);
return;
}
spread(node, l, r);
int mid = (l + r) / 2;
if (R <= mid) {
change(lson, l, mid, L, R, x);
}
else if (L > mid) {
change(rson, mid + 1, r, L, R, x);
}
else {
change(lson, l, mid, L, mid, x);
change(rson, mid + 1, r, mid + 1, R, x);
}
push_up(node);
}
看一看这串代码:函数中的参数,node,l,r 是一块的,代表子树节点node所代表的区间(l,r)。之后的L,R,x代表对在区间(L,R)内的数进行+x操作。这显然是个递归操作,当(l==L and r==R)时return,因为此时就能找到一个节点所代表的区间(l,r)完全被修改。倘若没有找到这样的区间,即本来的node节点与它代表的区间不符合要求,那么接下来就下放懒标记,将它下放到node的左子树和右子树上。下放后再二分区间通过L,R和mid的关系找到对应的区间去进行修改。记住每次对一个区间进行修改后都要去进行更新sum的操作。
查询操作
修改完后就是进行查询操作了!
为什么要讲查询操作呢?因为区间修改和普通的单点修改后的查询操作很相似,但是却有不一样的地方:
long long query(int node, int l, int r, int L, int R) {//查询操作
//查询l,r内的下标为node的sum,并返回sum的值
if (l == L && r == R) {
return sum[node];
}
spread(node, l, r);
int mid = (l + r) >> 1;
if (R <= mid) {
return query(lson, l, mid, L, R);
}
else if (L > mid) {
return query(rson, mid + 1, r, L, R);
}
else {
long long ans = 0;
ans += query(lson, l, mid, L, mid);
ans += query(rson, mid + 1, r, mid + 1, R);
return ans;
}
}
发现不同了吗?这儿的每次查询都要下放懒标记,这和前面提到过的单点查询的原理是相同的。