区间修改和单点修改的区别
还是上一章的涉及修改的区间和查询
如果把单点修改的算法延续到区间修改
即 [x, y] 看作是 (y - x + 1) 次单点修改,复杂度甚至会超越暴力求解
这时候就要引入 lazy 标记 ,这是单点修改没有的
lazy标记是什么?
举一个例子:
班级 4 个人交了 100 元班费
财务只需要在账面上记录 [1, 4] + 100 即可
4 个人买教材花了 50 元
财务只需要把标记改成 [1, 4] +50 即可
把原本对 4 个人要分别进行的操作改称对这个区间加一个标记
现在 [1, 2] 号两个同学要补办校服花费 30 元
这笔钱不能让剩下两个同学帮他们承担
所以作为财务,你选择把对 [1, 4] 的标记向下分别传递给 [1, 2] 和 [3, 4]
此时 [1, 4] 上的标记改成 0,[1, 2] 和 [3, 4] 都加上 50
此时我们再把 [1, 2] 的标记减去 30 代表他们两个人补校服的花费
同理后续需要对 1 进行操作前要将 [1, 2] 这个标记分别传给 1 和 2
我们把中间这个作为缓冲的标记称为 lazy 标记
每区间真实账面都是不准确的,因为还有部分操作存在 lazy 当中
可是在操作每个区间时我都希望这个区间的数值是准确的
所以我们要在操作区间前把 lazy 标记向下传递
区间操作包括查询和新的修改,
这时可以写一个 pushdown 函数
void pushdown(int p, int l, int r) {
int mid = (l + r) / 2;
lazy[2*p] += lazy[p];
lazy[2*p+1] += lazy[p];
tree[2*p] += lazy[p] * (mid - l + 1);
tree[2*p+1] += lazy[p] * (r - (mid + 1) +1);
lazy[p] = 0;
}
在什么时候使用 pushdown()
明确 pushdown 的作用:把当前区间的 lazy 标记传给下一层的两个区间
lazy 标记的本质就是对大区间的修改,大区间下面分属的两个小区间还不知道
(大区间是知道的这个修改的,这个容易混淆,我一开始也以为 lazy 不为 0 的区间的 sum 是当前 sum 加上 区间长度*lazy,但事实上lazy不为 0 是代表他下面分属的两个小区间还没收到修改的命令,这个我表述不太清楚,但看完具体代码应该算很容易明白的)
所以在查询和修改一个区间时,我希望这个区间维护的信息是正确的
所以每次操作新区间前先把上一层的lazy传下去
区间修改的左右子树递归可以参考区间查询的左右子树递归,二者的原理是相同的
void change(int p, int l, int r, int x, int y, int num) {
if (x<=l && r<=y) {
tree[p] += (r - l + 1) * num;
lazy[p] += num;
return;
}
if (lazy[p] != 0)
pushdown(p, l, r);
int mid = (l + r) / 2;
if (x <= mid)
change(p, l, mid, x, y, num);
if (y >= mid+1)
change(p, mid+1, r, x, y, num);
tree[p] = tree[2*p] + tree[2*p+1];
}
区间查询的代码也与模板一的无异
只需要注意在递归下一层之前把 lazy 标记先传下去
int calc(int p, int l, int r, int x, int y) {
if (x<=l && r<=y){
return tree[p];
}
if (lazy[p] != 0)
pushdown(p, l, r);
int res = 0;
if (x <= mid)
res += calc(2*p, l, mid, x, y)
if (y >= mid+1)
res += calc(2*p+1, mid+1, r, x, y)
return res;
}
总结
下面是模板 2 的完整代码
不要学习我的#define int long long
这是个坏习惯容易被卡常数
但我说实话一道题卡常把 ll 换成 int 就过了那他卡的真没水平
非要用注意把 main 函数的返回值改成 signed
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define LNF 1e18
#define INF 0x3f3f3f3f
#define abs(x) llabs(x)
int arr[100001];
int tree[400001];
int lazy[400001]={0};
void build (int p, int l, int r) {
if (l == r) {
tree[p] = arr[l];
return;
}
int mid = (l + r) / 2;
build (2*p, l, mid);
build (2*p+1, mid+1, r);
tree[p] = tree[2*p] + tree[2*p+1];
}
void pushdown (int p, int l, int r){
int mid = (l + r) / 2;
lazy[2*p] += lazy[p];
lazy[2*p+1] += lazy[p];
tree[2*p] += (mid - l +1) * lazy[p];
tree[2*p+1] += (r - (mid+1) +1) * lazy[p];
lazy[p] = 0;
}
void change (int p, int l, int r, int x, int y, int num) {
if (x<=l && r<=y) {
tree[p] += num * (r-l+1);
lazy[p] += num;
return;
}
if (lazy[p] != 0){
pushdown(p, l, r);
}
int mid = (l + r) / 2;
if (x <= mid) {
change (2*p, l, mid, x, y, num);
}
if (y >= mid+1) {
change (2*p+1, mid+1, r, x, y, num);
}
tree[p] = tree[2*p] +tree[2*p+1];
}
int calc (int p, int l, int r, int x, int y) {
if (x<=l && r<=y) {
return tree[p];
}
if (lazy[p] != 0) {
pushdown (p, l, r);
}
int res = 0;
int mid = (l + r) / 2;
if (x <= mid) res += calc(2*p, l, mid, x, y);
if (y >= mid+1) res += calc(2*p+1, mid+1, r, x, y);
return res;
}
void solve(){
int n,q;cin >> n >> q;
for (int i=1;i<=n;i++) cin >> arr[i];
build (1, 1, n);
while (q--) {
int op;cin >> op;
if (op == 1) {
int x, y, num;
cin >> x >> y >> num;
change(1, 1, n, x, y, num);
}else if (op == 2){
int x, y;
cin >> x >> y;
cout << calc(1, 1, n, x, y) << endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int T=1;//cin >> T;
while (T--) solve();
}