區間修改、區間加值
出處:https://slides.com/sylveon/cp2019-spring
出處:https://slides.com/sylveon/cp2019-spring
有數列 \(a_1, a_2, a_3, \cdots a_n\) ,想要做 \(Q\) 件事
求區間和我會,但區間加值呢?
將一個區間標記起來
代表這個區間中每個元素都要做「某種操作」
[1, 4]被打上了「7」這個lazy tag
則代表 \(a_1, a_2, \cdots a_4\)中每個值都要加上 7
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
+28
+14
+7
但實際上沒有往下做!
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
2
8
1~8
1~4
7
5~8
5~6
7~8
7
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
3~4
怎麼辦?
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~4
1~2
7
3~4
7
3~4
7
5~8
5~6
要將 \(a_L, a_{L+1}, \cdots, a_R\) 都加上 \(k\) 的話
因為一次修改完全部太花時間了
所以先暫時不做,等到需要的時候再往下推
1~8
1~4
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
3~4
13
5~8
5~6
5
13
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
1~2
?
3~4
?
要分別填上甚麼數字呢?
5~8
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
7
3~4
7
3
?
4
?
5~8
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
7
3
7
4
20
5~8
5~6
13
1~8
1~4
7
5~8
const int N = 500000;
int arr[4 * N + 10];
int tag[4 * N + 10];開兩個陣列,分別存節點的區間和與lazy tag
seg[0] = 10 + 7 = 17
seg[1] = 10
seg[2] = 7
int pull(int x, int y) {
return x + y;
}pull 與原本的線段樹一樣
#define IL(X) ((X)*2+1)
#define IR(X) ((X)*2+2)
void build(int L, int R, int id) {
if (L == R) {
arr[id] = a[L];
return;
}
int M = (L + R) / 2;
build(L , M, IL(id));
build(M+1, R, IR(id));
arr[id] = pull(arr[IL(id)], arr[IR(id)]);
}void push(int L, int R, int id) {
arr[id] += tag[id] * (R - L + 1);
if (L != R) {
tag[IL(id)] += tag[id];
tag[IR(id)] += tag[id];
}
tag[id] = 0;
}(2)
注意因為是區間和
所以要乘上 \(R-L+1\)
(3)
如果 \(L == R\) 的話
代表到葉節點了
所以不必將lazy tag往下推
(7)
最後記得將這個節點的lazy tag歸零
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
5~8
1~2
7
3~4
7
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
3~4
5~8
1~2
7
3~4
7~8
5~6
1
3
7
4
7
5
6
7
8
2
1~8
1~4
已經推過的lazy tag記得歸零
3
4
int Query(int l, int r, int L, int R, int id) {
push(L, R, id);
if (l == L && r == R)
return arr[id];
int M = (L + R) / 2;
if (r <= M)
return Query(l, r, L , M, IL(id));
if (M < l)
return Query(l, r, M+1, R, IR(id));
return pull(
Query(l , M, L , M, IL(id)),
Query(M+1, r, M+1, R, IR(id))
);
}(2)
路過的時候記得推lazy tag
其餘都一樣
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
路過就推一下
1~8
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
7
3~4
7
5~8
5~6
除了要路過推一下以外,和原本的一模一樣
void Modify(int l, int r, int k, int L, int R, int id) {
push(L, R, id);
if (l == L && r == R) {
tag[id] += k;
return;
}
int M = (L + R) / 2;
if (r <= M) Modify(l, r, k, L , M, IL(id));
else if (M < l) Modify(l, r, k, M+1, R, IR(id));
else {
Modify(l , M, k, L , M, IL(id));
Modify(M+1, r, k, M+1, R, IR(id));
}
push(L , M, IL(id));
push(M+1, R, IR(id));
arr[id] = pull(arr[IL(id)], arr[IR(id)]);
}(2)
路過的時候記得推一下
(4)
區間加值的時候
修改lazy tag即可
(15、16)
pull前
子孫的lazy tag
不一定是零
記得將子孫也push一下
其餘都一樣
1~8
1~4
7
5~8
1~2
3~4
7~8
21
5~6
1
3
4
5
6
7
8
2
1~8
1~4
7
5~8
3~4
7~8
21
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
7
3~4
7
5~8
3~4
7~8
21
5~6
1
3
4
5
6
7
8
2
1~8
1~4
1~2
7
3
7
4
20
5~8
5~6
13
pull 的時候要注意
3~4
7~8
21
1
3
5
6
13
7
8
2
1~8
1~4
1~2
7
3
7
4
20
5~8
5~6
13
5~6
7~8
5
13
6
7
21
8
21
因為和lazy tag有關的操作都是
遇到的時候「順便」做一下的
因此複雜度與原本的線段樹一樣,都是: