序列問題
區間修改、區間加值
複習:何謂線段樹

出處:https://slides.com/sylveon/cp2019-spring

出處:https://slides.com/sylveon/cp2019-spring
區間加值、區間詢問
有數列 \(a_1, a_2, a_3, \cdots a_n\) ,想要做 \(Q\) 件事
- 求 \(a_i+a_{i+1}+\cdots+a_j\)
- 將 \(a_L, a_{L+1}, \cdots, a_R\) 中每個數字都加上 \(k\)
求區間和我會,但區間加值呢?
Lazy Tag的概念
將一個區間標記起來
代表這個區間中每個元素都要做「某種操作」
[1, 4]被打上了「7」這個lazy tag
則代表 \(a_1, a_2, \cdots a_4\)中每個值都要加上 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
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
+28
+14
+7
但實際上沒有往下做!
想求1~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~7的答案
1~8
1~4
7
5~8
1~2
3~4
7~8
5~6
1
3
4
5
6
7
8
2
想求3~6的答案?
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
怎麼辦?
將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~4
1~2
7
3~4
7
3~4
7
5~8
5~6
lazy tag
要將 \(a_L, a_{L+1}, \cdots, a_R\) 都加上 \(k\) 的話
因為一次修改完全部太花時間了
所以先暫時不做,等到需要的時候再往下推
將3~5通通加上13
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
將4~6加上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
?
要分別填上甚麼數字呢?
將4~6加上13
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
?
將4~6加上13
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
lazy tag
- 將區間標記起來,代表「整個區間都要做某個操作」
- 未來路過那個區間的時候,再往下推就好
- 每個操作的複雜度和原本的線段樹一樣: \(O(\log N)\)
lazy tag
1~8
1~4
7
5~8
const int N = 500000;
int arr[4 * N + 10];
int tag[4 * N + 10];開兩個陣列,分別存節點的區間和與lazy tag
pull 與原本一樣
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)]);
}將lazy tag往下推
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歸零
想要求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~8
1~4
7
想要求4的答案
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
想要求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
其餘都一樣
想求3~6的答案
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~6的答案
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一下
其餘都一樣
將4~6加上13
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
將4~6加上13
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
將4~6加上13
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 的時候要注意
以5~8為例子
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有關的操作都是
遇到的時候「順便」做一下的
因此複雜度與原本的線段樹一樣,都是:
- 建造: \(O(N)\)
- 修改: \(O(\log N)\)
- 查詢: \(O(\log N)\)
總結
練習題
zerojudge
- d539 - 區間加值、區間最大值
- d799 - 區間加值、區間求和
TIOJ
- 1224 - 算一堆矩形的面積的聯集
- 1408
- 1836
Codeforces
- 292E
- 242E
- 301D
序列問題 - 區間修改、區間加值
By polarischiba
序列問題 - 區間修改、區間加值
清大程式設計競技社 進階班課程
- 335