【学习笔记】线段树
NCC79601
2019-03-02 12:06:33
## 例题 [P2023](https://www.luogu.org/problemnew/show/P2023)
就是这个线段树2的模板,我总是没有AC过,这次终于是AC了!
最需要注意的是:**在乘的时候,加法的$lazytag$也必须跟着更新;$pushdown$的时候,加法的$lazytag$也必须跟着更新**,不然就无法连续维护加操作和乘操作。
```cpp
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int MAXN = 100010;
struct SEGTREE {
int l, r;
LL v, add, mul;
} segtree[MAXN<<2];
int n, p, m;
LL a[MAXN];
void build_tree(int num, int l, int r) {
segtree[num].l = l;
segtree[num].r = r;
segtree[num].add = 0;
segtree[num].mul = 1;
if(l == r) {
segtree[num].v = (LL)a[l] % p;
return;
}
int mid = (l + r) >> 1;
build_tree(num<<1, l, mid);
build_tree(num<<1|1, mid+1, r);
segtree[num].v = (segtree[num<<1].v + segtree[num<<1|1].v) % p;
return;
}
void pushdown(int num) {
int lson = num<<1, rson = num<<1|1;
segtree[lson].v = (segtree[lson].v * segtree[num].mul + (segtree[num].add * (segtree[lson].r - segtree[lson].l + 1))) % p;
segtree[rson].v = (segtree[rson].v * segtree[num].mul + (segtree[num].add * (segtree[rson].r - segtree[rson].l + 1))) % p;
segtree[lson].add = (segtree[lson].add * segtree[num].mul + segtree[num].add) % p;
segtree[rson].add = (segtree[rson].add * segtree[num].mul + segtree[num].add) % p;
segtree[lson].mul = (segtree[lson].mul * segtree[num].mul) % p;
segtree[rson].mul = (segtree[rson].mul * segtree[num].mul) % p;
segtree[num].add = 0;
segtree[num].mul = 1;
return;
}
void edit_tree(bool add, int num, int l, int r, int c) {
int ll = segtree[num].l, rr = segtree[num].r;
if(add) {
if(ll >= l && rr <= r) {
segtree[num].v = (segtree[num].v + (c * (rr - ll + 1))) % p;
segtree[num].add = (segtree[num].add + c) % p;
return;
}
pushdown(num);
int mid = (ll + rr) >> 1;
if(l <= mid)
edit_tree(add, num<<1, l, r, c);
if(r > mid)
edit_tree(add, num<<1|1, l, r, c);
segtree[num].v = (segtree[num<<1].v + segtree[num<<1|1].v) % p;
} else {
if(ll >= l && rr <= r) {
segtree[num].v = (segtree[num].v * c) % p;
segtree[num].mul = (segtree[num].mul * c) % p;
segtree[num].add = (segtree[num].add * c) % p;
return;
}
pushdown(num);
int mid = (ll + rr) >> 1;
if(l <= mid)
edit_tree(add, num<<1, l, r, c);
if(r > mid)
edit_tree(add, num<<1|1, l, r, c);
segtree[num].v = (segtree[num<<1].v + segtree[num<<1|1].v) % p;
}
return;
}
LL query(int num, int l, int r) {
int ll = segtree[num].l, rr = segtree[num].r;
if(ll >= l && rr <= r)
return segtree[num].v % p;
pushdown(num);
int mid = (ll + rr) >> 1;
LL sum = 0;
if(l <= mid)
sum = (query(num<<1, l, r) + sum) % p;
if(r > mid)
sum = (query(num<<1|1, l, r) + sum) % p;
return sum;
}
int main() {
scanf("%d%d", &n, &p);
for(int i=1; i<=n; i++)
scanf("%lli", &a[i]);
build_tree(1, 1, n);
int opt, t, g, c;
scanf("%d", &m);
while(m--) {
scanf("%d", &opt);
switch(opt) {
case 1: {
scanf("%d%d%d", &t, &g, &c);
edit_tree(false, 1, t, g, c);
break;
}
case 2: {
scanf("%d%d%d", &t, &g, &c);
edit_tree(true, 1, t, g, c);
break;
}
case 3: {
scanf("%d%d", &t, &g);
printf("%lli\n", query(1, t, g)%p);
break;
}
}
}
return 0;
}
```
---
## 易错点更新
$edit\_tree$以后记得合并儿子信息!
**$pushdown$及$edit\_tree$时记得要把$tag$乘上区间长**
---
# 线段树维护区间修改/单点修改/区间最大值
## 例题 [P4315](https://www.luogu.org/problemnew/show/P4315)
恶心死了,查半天错才知道自己错在哪里。
这里的线段树维护了两个懒标记:$tree[x].tag$以及$tree[x].c$。$tree[x].tag$维护的是区间加,而$tree[x].c$维护的是区间修改。在$pushdown$的时候就要慎重考虑这两个玩意儿的顺序了,结论是:
## 先看$\textbf{tree[x].c}$,再看$\textbf{tree[x].tag}$。
**注意:** 在$cover(l,r,c)$的时候,会使当前$tree[x].tag=0$,这也就是说**肯定是在一次$\textbf{cover(l,r,c)}$操作之后,出现的$\textbf{tree[x].tag}$才会对线段树造成修改**,因此在$pushdown$的时候,如果发生了$cover(l,r,c)$操作,即$tree[x].c\neq-1$,一定是先把$tree[x].c$下传,将两儿子的$tree[x].tag$清空,再把$tree[x].tag$信息下传。
此思路**非常重要**,正确决定线段树$lazytag$的下传顺序才不会懒出毛病。
```cpp
inline void pushdown(int x) {
if(~tree[x].c) {
tree[x<<1].max = tree[x<<1|1].max = tree[x].c;
tree[x<<1].c = tree[x<<1|1].c = tree[x].c;
tree[x<<1].tag = tree[x<<1|1].tag = 0;
tree[x].c = -1;
}
if(tree[x].tag) {
tree[x<<1].max += tree[x].tag;
tree[x<<1|1].max += tree[x].tag;
tree[x<<1].tag += tree[x].tag;
tree[x<<1|1].tag += tree[x].tag;
tree[x].tag = 0;
}
return;
}
```
相应地,我们已经决定了$lazytag$的下传顺序,那么$cover(l,r,c)$和$add(l,r,c)$也要按照这个规则来打懒标记,也就是说 **$\textbf{cover(l,r,c)}$的时候需要把$\textbf{tree[x].tag=0}$** ,而$add(l,r,c)$则不需要做出调整。
```cpp
inline void cover(int x, int l, int r, int c) {
int ll = tree[x].l, rr = tree[x].r;
if(l <= ll && rr <= r) {
tree[x].max = tree[x].c = c;
tree[x].tag = 0;
return;
}
pushdown(x);
int mid = (ll + rr) >> 1;
if(l <= mid)
cover(x<<1, l, r, c);
if(r > mid)
cover(x<<1|1, l, r, c);
pushup(x);
return;
}
inline void add(int x, int l, int r, int c) {
int ll = tree[x].l, rr = tree[x].r;
if(l <= ll && rr <= r) {
tree[x].max += c;
tree[x].tag += c;
return;
}
pushdown(x);
int mid = (ll + rr) >> 1;
if(l <= mid)
add(x<<1, l, r, c);
if(r > mid)
add(x<<1|1, l, r, c);
pushup(x);
return;
}
```
**谨记**:慎重考虑懒标记的下传顺序、修改时打懒标记的规则!
---
完整代码
```cpp
//code
inline void pushdown(int x) {
if(~tree[x].c) {
tree[x<<1].max = tree[x<<1|1].max = tree[x].c;
tree[x<<1].c = tree[x<<1|1].c = tree[x].c;
tree[x<<1].tag = tree[x<<1|1].tag = 0;
tree[x].c = -1;
}
if(tree[x].tag) {
tree[x<<1].max += tree[x].tag;
tree[x<<1|1].max += tree[x].tag;
tree[x<<1].tag += tree[x].tag;
tree[x<<1|1].tag += tree[x].tag;
tree[x].tag = 0;
}
return;
}
inline void pushup(int x) {
tree[x].max = max(tree[x<<1].max, tree[x<<1|1].max);
return;
}
inline void build_tree(int x, int l, int r) {
tree[x].l = l, tree[x].r = r;
tree[x].tag = 0, tree[x].c = -1;
if(l == r) {
tree[x].max = w[rev[l]];
return;
}
int mid = (l + r) >> 1;
build_tree(x<<1, l, mid);
build_tree(x<<1|1, mid + 1, r);
pushup(x);
return;
}
inline void cover(int x, int l, int r, int c) {
int ll = tree[x].l, rr = tree[x].r;
if(l <= ll && rr <= r) {
tree[x].max = tree[x].c = c;
tree[x].tag = 0;
return;
}
pushdown(x);
int mid = (ll + rr) >> 1;
if(l <= mid)
cover(x<<1, l, r, c);
if(r > mid)
cover(x<<1|1, l, r, c);
pushup(x);
return;
}
inline void add(int x, int l, int r, int c) {
int ll = tree[x].l, rr = tree[x].r;
if(l <= ll && rr <= r) {
tree[x].max += c;
tree[x].tag += c;
return;
}
pushdown(x);
int mid = (ll + rr) >> 1;
if(l <= mid)
add(x<<1, l, r, c);
if(r > mid)
add(x<<1|1, l, r, c);
pushup(x);
return;
}
inline int query(int x, int l, int r) {
int res = -INF;
int ll = tree[x].l, rr = tree[x].r;
if(l <= ll && rr <= r)
return tree[x].max;
pushdown(x);
int mid = (ll + rr) >> 1;
if(l <= mid)
res = max(res, query(x<<1, l, r));
if(r > mid)
res = max(res, query(x<<1|1, l, r));
return res;
}
```
---
## 例题 [P3740](https://www.luogu.org/problemnew/show/P3740)
这道题是维护区间染色。具体做法为直接开一个$clr[]$来维护当前区间有没有被完全覆盖。可以知道的是,如果一个海报所处的区间被后来的海报完全覆盖,那么这个海报就没法被看见了。例如:
![image.png](https://img.ffis.me/images/2019/08/09/image.png)
图中$[3,4]$的这块海拔被遮完了,因此它没法被看见。
考虑将这个过程**离线**(在线的话可参考[树链剖分](https://ncc79601.blog.luogu.org/tree-chain-split)部分对[P2146](https://www.luogu.org/problem/P2146)的题解),那么每次只需要查询当前海报区间是否已经被完全覆盖就可以了。因为根本不需要建树,所以复杂度为稳定$O(logn)$,对付这个数据也足够了。
```cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e7 + 10, MAXM = 1e3 + 10;
bool clr[MAXN << 2], flag;
int n, m, a[MAXM], b[MAXM], ans = 0;
void pushup(int x) {
clr[x] = clr[x<<1] & clr[x<<1|1];
}
void edit_tree(int x, int l, int r, int L, int R) {
if(clr[x])
return;
if(L <= l && r <= R) {
flag = clr[x] = true;
return;
}
int mid = (l + r) >> 1;
if(L <= mid)
edit_tree(x<<1, l, mid, L, R);
if(R > mid)
edit_tree(x<<1|1, mid + 1, r, L, R);
pushup(x);
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d %d", &a[i], &b[i]);
for(int i = m; i; i--) {
flag = false;
edit_tree(1, 1, n, a[i], b[i]);
if(flag)
ans++;
}
printf("%d", ans);
return 0;
}
```