线段树 区间开方区间求和 & 区间赋值、加、查询

时间:2023-03-08 17:00:23
线段树 区间开方区间求和 & 区间赋值、加、查询

本文同步发表于 https://www.zybuluo.com/Gary-Ying/note/1288518

线段树的小应用 —— 维护区间开方区间求和

题目传送门

约定: sum(i,j) 表示区间 [i,j] 中所有元素的和,也就是\(\Sigma_{k=i}^j a_k\)

这个维护思想来自 分块 ;线段树维护区间开方的难点就在于我们没有办法很方便地维护区间的和,具体来说,如果我们对区间 [l,r] 进行开方,我们并不能从 sum(l,r) 推到 sum'(l,r)

这就比较麻烦了,我们不能很快地维护区间的和,而将来查询的时候又有可能会用到这一整段的和,所以我们必须维护它。

既然不能整段地去求,也就意味着我们只能 一个一个地求 ,我们只能对每个元素开方后再相加。

这是很暴力的做法,时间复杂度有保证吗?

如果单纯地每次修改都暴力修改,时间复杂度是 \(O(n^2)\) 的,但是我们发现,对于 int(long int) 范围内的数字,最多开 5 次二次方就会变成 0 或 1,也就是每个数字暴力开方的次数不会超过 5 次,这样时间复杂度就有保证了。

时间复杂度大概是 \(O(n log_2^n)\)。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm> const int maxn = 50007;
int n, a[maxn], sum[maxn << 2];
bool flag[maxn << 2]; inline void PushUp(int rt){
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
flag[rt] = flag[rt<<1] & flag[rt<<1|1];
} void build(int rt, int l, int r){
if (l == r){
sum[rt] = a[l];
flag[rt] = (a[l] == 0) || (a[l] == 1);
return;
}
int m = (l + r) >> 1;
build(rt << 1, l, m);
build(rt << 1 | 1, m + 1, r);
PushUp(rt);
} inline void update(int rt, int l, int r){
for (int i = l; i <= r; ++i) a[i] = sqrt(a[i]);
build(rt, l, r);
} void xSqrt(int rt, int l, int r, int L, int R){
if (L <= l && r <= R){
if (flag[rt]) return;
else update(rt, l, r);
return;
}
int m = (l + r) >> 1;
if (L <= m) xSqrt(rt<<1, l, m, L, R);
if (R > m) xSqrt(rt<<1|1, m+1, r, L, R);
PushUp(rt);
} int query(int rt, int l, int r, int L, int R){
if (L <= l && r <= R) return sum[rt];
int m = (l + r) >> 1, res = 0;
if (L <= m) res += query(rt<<1, l, m, L, R);
if (R > m) res += query(rt<<1|1, m+1, r, L, R);
return res;
} int main(){
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
build(1, 1, n);
for (int t = 1; t <= n; ++t){
int opt, l, r, c; scanf("%d%d%d%d", &opt, &l, &r, &c);
if (opt == 0) xSqrt(1, 1, n, l, r);
else printf("%d\n", query(1, 1, n, l, r));
}
return 0;
}

线段树的小应用 —— 区间赋值、加、查询

线段树对于一个区间有 多个 操作时,处理 操作的优先级 就显得十分重要了。

对于这道题,我们需要分析 赋值 两种操作谁的优先级更高比较好,换句话说,我们要考虑 如何合并懒惰标记 。为了维护区间赋值、加这两种操作,我们需要两个标记:修改标记 & 加标记

标记的优先级指的是如果同样存在两个标记,先执行的标记是谁,举个栗子:如果定义 修改标记 优先级高,那么对于原值,操作后的值就是先修改再加;如果定义 加标记 优先级高,那么就是先加再修改,我们发现这时候加标记已经没有了意义,所以我们如果定义 修改标记 优先级高。

如果对于区间 \([L,R]\) ,已经有了修改标记:

  • 如果要执行 修改操作 直接更新 修改标记
  • 如果要执行 加操作 直接更新 修改标记

如果对于区间 \([L,R]\) ,已经有了加标记:

  • 如果要执行 修改操作 直接更新 修改标记,清空 加标记
  • 如果要执行 加操作 直接更新 加标记

我们发现,加标记修改标记 其实并不会共存。

END,请看代码

(相关题目:Luogu4315 月下“毛景树”,需要用线段树支持树剖)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std; const int maxn = 100007;
int Max[maxn << 2], changetag[maxn << 2], addtag[maxn << 2];
int a[maxn];
int n, Q; inline void PushUp(int rt){
Max[rt] = max(Max[rt<<1], Max[rt<<1|1]);
} inline void PushDown(int rt, int ln, int rn){
if (changetag[rt] != -1){
Max[rt<<1] = changetag[rt]; Max[rt<<1|1] = changetag[rt];
changetag[rt << 1] = changetag[rt]; addtag[rt << 1] = 0;
changetag[rt << 1 | 1] = changetag[rt]; addtag[rt << 1 | 1] = 0;
changetag[rt] = -1;
}else if (addtag[rt]){
Max[rt<<1] += addtag[rt]; Max[rt<<1|1] += addtag[rt];
if (changetag[rt<<1] != -1) changetag[rt<<1] += addtag[rt];
else addtag[rt<<1] += addtag[rt];
if (changetag[rt<<1|1] != -1) changetag[rt<<1|1] += addtag[rt];
else addtag[rt<<1|1] += addtag[rt];
addtag[rt] = 0;
}
} void build(int rt, int l, int r){
changetag[rt] = -1; addtag[rt] = 0;
if (l == r){
Max[rt] = a[l];
return;
}
int m = (l + r) >> 1;
build(rt<<1, l, m);
build(rt<<1|1, m+1, r);
PushUp(rt);
} void change(int rt, int l, int r, int L, int R, int C){
if (L <= l && r <= R){
Max[rt] = C;
changetag[rt] = C;
addtag[rt] = 0;
return;
}
int m = (l + r) >> 1;
PushDown(rt, m - l + 1, r - m);
if (L <= m) change(rt<<1, l, m, L, R, C);
if (R > m) change(rt<<1|1, m+1, r, L, R, C);
PushUp(rt);
} void add(int rt, int l, int r, int L, int R, int C){
if (L <= l && r <= R){
Max[rt] = Max[rt] + C;
if (changetag[rt] == -1) addtag[rt] += C;
else changetag[rt] += C;
return;
}
int m = (l + r) >> 1;
PushDown(rt, m - l + 1, r - m);
if (L <= m) add(rt<<1, l, m, L, R, C);
if (R > m) add(rt<<1|1, m+1, r, L, R, C);
PushUp(rt);
} int query(int rt, int l, int r, int L, int R){
if (L <= l && r <= R) return Max[rt];
int m = (l + r) >> 1, res = -1;
PushDown(rt, m - l + 1, r - m);
if (L <= m) res = max(res, query(rt<<1, l, m, L, R));
if (R > m) res = max(res, query(rt<<1|1, m+1, r, L, R));
return res;
} int main(){
scanf("%d%d", &n, &Q);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
build(1, 1, n);
while (Q--){
int opt, l, r, val; scanf("%d%d%d%d", &opt, &l, &r, &val);
if (opt == 1) change(1, 1, n, l, r, val);
else if (opt == 2) add(1, 1, n, l, r, val);
else printf("%d\n", query(1, 1, n, l, r));
}
return 0;
}