segment tree
迭代式線段樹
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|
| 2 | 4 | 6 | 8 |
|---|
假設題目是區間最大值:
| 4 | 8 |
|---|
| 8 |
|---|
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|
| 4 | 5 | 6 | 7 |
|---|
怎麼蓋?
用一維陣列存就好了
| 2 | 3 |
|---|
| 1 |
|---|
index:
(原陣列)
讀取測資時,\(a[i]\) 的值放在 \(a[i+n]\) 上
而\(a[i]=max(a[i \times 2],a[i \times 2+1]), i < n\)
void init(int n){
for(int i = 0 ; i < n ; i++){
tr[i+n] = arr[i];
}
for(int i = n ; i >= 1 ; i--){
tr[i] = max(tr[i<<1], tr[i<<1+1]);
}
}注意 tr 的大小是 \(2n\)
被修改到的都是他的所有祖先
\(a[i]\) 的祖先是 \(a[i/2]\)
所以一路往上修改就好了
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|
| 4 | 5 | 6 | 7 |
|---|
| 2 | 3 |
|---|
| 1 |
|---|
void update(int pos, int val){
pos += n;
tr[pos] = val;
for(int i = pos >> 1 ; i >= 1;i >>= 1){
tr[i] = max(tr[i<<1], tr[i<<1|1]);
}
}| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|
| 4 | 5 | 6 | 7 |
|---|
| 2 | 3 |
|---|
| 1 |
|---|
左界與右界各有兩種情況
左界為右節點:將\(a[l]\)單獨計算,然後往右推一格 \(l++\)
右界為左節點:將\(a[r]\)單獨計算,然後往左推一格 \(r--\)
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|
| 4 | 5 | 6 | 7 |
|---|
| 2 | 3 |
|---|
| 1 |
|---|
左界為左節點:直接往上推一格 \(l /= 2\)
右界為右節點:直接往上推一格 \(r /= 2\)
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|
| 4 | 5 | 6 | 7 |
|---|
| 2 | 3 |
|---|
| 1 |
|---|
舉個例子
最後 \(l > r \) 時終止迴圈
int query(int l, int r){
int res = 0;
l += n;
r += n;
for(; l <= r ; l>>=1, r>>=1){
if(l & 1) {
res = max(res, tr[l]);
l++;
}
if(!(r & 1)) {
res = max(res, tr[r]);
r--;
}
}
return res;
}int query(int l, int r){
int res = 0;
l += n;
r += n;
for(; l <= r ; l>>=1, r>>=1){
if(l & 1) {
res = max(res, tr[l++]);
}
if(!(r & 1)) {
res = max(res, tr[r--]);
}
}
return res;
}這是左閉右閉的寫法
int query(int l, int r){
int res = 0;
l += n;
r += n;
for(; l < r ; l>>=1, r>>=1){
if(l & 1) {
res = max(res, tr[l]);
l++;
}
if(r & 1) {
r--;
res = max(res, tr[r]);
}
}
return res;
}int query(int l, int r){
int res = 0;
l += n;
r += n;
for(; l < r ; l>>=1, r>>=1){
if(l & 1) {
res = max(res, tr[l++]);
}
if(r & 1) {
res = max(res, tr[--r]);
}
}
return res;
}這是左閉右開的寫法
4n
void build(int s, int t, int p) {
// 對 [s,t] 區間建立線段樹,當前根的編號為 p
if (s == t) {
tree[p] = a[s];
return;
}
int m = s + ((t - s) >> 1); //中點
build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
tree[p] = max(tree[p * 2], tree[(p * 2) + 1]);
}由於遞迴式線段樹會產生無用的葉節點
所以大小要開到 4n
void update(int p, int s, int t, int idx, int val) {
// a[idx] += val, p 為當前節點 index, [s,t]為當前節點區間
// 1. 終止條件:到達葉節點
if (s == t) {
tree[p] += val; // 更新葉節點的值
return;
}
int mid = s + (t - s) / 2;
int left_child = 2 * p; // 左節點 index
int right_child = 2 * p + 1; // 右節點 index
if (idx <= mid) {
update(left_child, s, mid, idx, val);
} else {
update(right_child, mid + 1, t, idx, val);
}
// 向上更新
tree[p] = max(tree[left_child], tree[right_child]);
}他會跑過所有需要更新的點,然後一路從葉節點往上更新回根節點
int query(int l, int r, int s, int t, int p) {
// [l, r] 為查詢區間, [s, t] 為當前節點包含的區間, p 為當前節點的編號
if (l <= s && t <= r)
return tree[p]; // 當前區間為詢問區間的子集時直接返回當前區間的和
int m = s + ((t - s) >> 1), ans = -(1e9+7);
if (l <= m) ans = max(ans, query(l, r, s, m, p * 2));
// 若左節點代表的區間 [s, m] 與詢問區間有交集, 則遞迴查詢左節點
if (r > m) ans = max(ans, query(l, r, m + 1, t, p * 2 + 1));
// 若右節點代表的區間 [m + 1, t] 與詢問區間有交集, 則遞歸查詢右節點
return ans;
}查詢時,是從根節點一路往下跑
也就是
query(l, r, 1, n, 1); //1-based這裡所有的函式,傳入的值都要是1-based
我好懶我不想這麼快更新
題目改成區間和,但是我們有區間改值這一個動作
很明顯你不能直接砸 \(n\) 次單點修改
怎麼做?
| 3 | 2 | 1 | 6 | 8 | 9 | 4 | 7 |
|---|
| 5 | 7 | 17 | 11 |
|---|
| 12 | 28 |
|---|
| 40 |
|---|
從根節點開始
根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集
如果紅色區間的值要 +3
往下推
根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集
往下推
根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)
根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集
往下推
根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)
(+3)
根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)
(+3)
(+3)
| 5 | 13 | 17 | 11 |
|---|
| 5 | 13 | 23 | 11 |
|---|
| 3 | 2 | 1 | 6 | 8 | 9 | 7 | 7 |
|---|
| 5 | 13 | 17 | 11 |
|---|
| 5 | 13 | 17 | 11 |
|---|
| 12 | 28 |
|---|
| 40 |
|---|
然後向上更新
(+3)
(+3)
(+3)
| 5 | 13 | 23 | 11 |
|---|
| 3 | 2 | 1 | 6 | 8 | 9 | 7 | 7 |
|---|
| 5 | 13 | 23 | 14 |
|---|
| 18 | 37 |
|---|
| 55 |
|---|
void push_up(int p){
int left_child = p * 2, right_child = p * 2 + 1;
tree[p] = tree[left_child] + tree[right_child];
return;
}
void update_range(int p, int s, int t, int L, int R, long long val) {
// p為當前節點,[s,t]為當前節點之區間,[L,R]為修改之區間,val為修改的數值
// 如果當前區間包含在目標區間內
if (L <= s && t <= R) {
// 更新當前節點的區間和:區間長度 * val
tree[p] += val * (t - s + 1);
// 打上懶標 (累加)
lazy[p] += val;
return;
}
// 這個東西等等會講
push_down(p, s, t);
int mid = s + (t - s) / 2;
int left_child = 2 * p;
int right_child = 2 * p + 1;
if (L <= mid) {
// 目標區間與左子節點有交集
update_range(left_child, s, mid, L, R, val);
}
if (R > mid) {
// 目標區間與右子節點有交集
update_range(right_child, mid + 1, t, L, R, val);
}
// 向上更新
push_up(p);
}| 3 | 2 | 1 | 6 | 8 | 9 | 7 | 7 |
|---|
| 5 | 13 | 23 | 14 |
|---|
| 18 | 37 |
|---|
| 55 |
|---|
(+3)
(+3)
(+3)
從根節點開始
根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集
往下推
根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集
往下推
根節點的區間不包含在紅色區間內,左子節點與紅色區間有交集
把懶標往下傳,位置往下推
根節點的區間包含在紅色區間內,回傳自己的值
根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集
往下推
根節點的區間不包含在紅色區間內,左子節點與紅色區間有交集
把懶標往下傳,位置往下推
根節點的區間包含在紅色區間內,回傳自己的值
| 3 | 2 | 4 | 9 | 8 | 9 | 7 | 7 |
|---|
| 3 | 2 | 4 | 9 | 11 | 12 | 7 | 7 |
|---|
| 3 | 2 | 4 | 9 | 8 | 9 | 7 | 7 |
|---|
| 3 | 2 | 4 | 9 | 8 | 9 | 7 | 7 |
|---|
| 3 | 2 | 4 | 9 | 8 | 9 | 7 | 7 |
|---|
| 3 | 2 | 4 | 9 | 8 | 9 | 7 | 7 |
|---|
void push_down(int node, int start, int end) {
if (lazy[node] != 0) { // 如果有懶標才需要下傳
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
// 更新左右子節點的區間和
// 區間內每個數字都加上 lazy[node],所以總和要加上(區間長度 * lazy[node])
tree[left_child] += lazy[node] * (mid - start + 1);
tree[right_child] += lazy[node] * (end - mid);
// 將懶標記累加給左右子節點
lazy[left_child] += lazy[node];
lazy[right_child] += lazy[node];
// 清空當前節點的懶標記
lazy[node] = 0;
}
}
long long query_range(int node, int start, int end, int L, int R) {
// 如果當前區間包含在目標查詢區間內
if (L <= start && end <= R) {
return tree[node];
}
// 如果當前區間不包含在目標查詢區間內,就把當前節點的懶標下傳,再往下找
push_down(node, start, end);
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
long long sum = 0;
if (L <= mid) {
// 目標區間與左子節點有交集
sum += query_range(left_child, start, mid, L, R);
}
if (R > mid) {
// 目標區間與右子節點有交集
sum += query_range(right_child, mid + 1, end, L, R);
}
return sum;
}懶標要開的跟線段樹一樣大 (4n)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
long long int tree[N * 4];
long long int lazy[N * 4];
int a[N * 4];
void push_up(int p){
int left_child = p * 2, right_child = p * 2 + 1;
tree[p] = tree[left_child] + tree[right_child];
return;
}
void push_down(int node, int start, int end) {
if (lazy[node] != 0) { // 如果有懶標才需要下傳
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
// 更新左右子節點的區間和
// 區間內每個數字都加上 lazy[node],所以總和要加上(區間長度 * lazy[node])
tree[left_child] += lazy[node] * (mid - start + 1);
tree[right_child] += lazy[node] * (end - mid);
// 將懶標記累加給左右子節點
lazy[left_child] += lazy[node];
lazy[right_child] += lazy[node];
// 清空當前節點的懶標記
lazy[node] = 0;
}
}
void update_range(int p, int s, int t, int L, int R, long long val) {
// p為當前節點,[s,t]為當前節點之區間,[L,R]為修改之區間,val為修改的數值
// 如果當前區間包含在目標區間內
if (L <= s && t <= R) {
// 更新當前節點的區間和:區間長度 * val
tree[p] += val * (t - s + 1);
// 打上懶標 (累加)
lazy[p] += val;
return;
}
// 這個東西等等會講
push_down(p, s, t);
int mid = s + (t - s) / 2;
int left_child = 2 * p;
int right_child = 2 * p + 1;
if (L <= mid) {
// 目標區間與左子節點有交集
update_range(left_child, s, mid, L, R, val);
}
if (R > mid) {
// 目標區間與右子節點有交集
update_range(right_child, mid + 1, t, L, R, val);
}
// 向上更新
push_up(p);
}
long long query_range(int node, int start, int end, int L, int R) {
// 如果當前區間包含在目標查詢區間內
if (L <= start && end <= R) {
return tree[node];
}
// 如果當前區間不包含在目標查詢區間內,就把當前節點的懶標下傳,再往下找
push_down(node, start, end);
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
long long sum = 0;
if (L <= mid) {
// 目標區間與左子節點有交集
sum += query_range(left_child, start, mid, L, R);
}
if (R > mid) {
// 目標區間與右子節點有交集
sum += query_range(right_child, mid + 1, end, L, R);
}
return sum;
}
void build(int s, int t, int p) {
// 對 [s,t] 區間建立線段樹,當前根的編號為 p
if (s == t) {
tree[p] = a[s];
return;
}
int m = s + ((t - s) >> 1); //中點
build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[(p * 2) + 1];
}
int main()
{
int n, q;
cin >> n >> q;
for (int i = 1 ; i <= n ; i++) {
cin >> a[i];
}
build(1, n, 1);
int m;
while(q--){
cin >> m;
if(m == 1){
int l, r, v;
cin >> l >> r >> v;
update_range(1, 1, n, l, r, v);
}
else{
int p;
cin >> p;
cout << query_range(1, 1, n, p, p) << '\n';
}
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
long long int tree[N * 4];
long long int lazy_add[N * 4];
long long int lazy_set[N * 4];
bool lazy_seted[N * 4];
int a[N * 4];
void push_up(int p){
int left_child = p * 2, right_child = p * 2 + 1;
tree[p] = tree[left_child] + tree[right_child];
return;
}
void push_down(int node, int start, int end) {
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
if (lazy_seted[node]) {
lazy_seted[left_child] = true;
lazy_seted[right_child] = true;
lazy_add[left_child] = 0;
lazy_add[right_child] = 0;
tree[left_child] = lazy_set[node] * (mid - start + 1);
tree[right_child] = lazy_set[node] * (end - mid);
lazy_set[left_child] = lazy_set[node];
lazy_set[right_child] = lazy_set[node];
lazy_seted[node] = false;
}
if (lazy_add[node] != 0) {
tree[left_child] += lazy_add[node] * (mid - start + 1);
tree[right_child] += lazy_add[node] * (end - mid);
if(lazy_seted[left_child]){
lazy_set[left_child] += lazy_add[node];
}
else{
lazy_add[left_child] += lazy_add[node];
}
if(lazy_seted[right_child]){
lazy_set[right_child] += lazy_add[node];
}
else{
lazy_add[right_child] += lazy_add[node];
}
lazy_add[node] = 0;
}
}
void update_range_add(int p, int s, int t, int L, int R, long long val) {
if (L <= s && t <= R) {
tree[p] += val * (t - s + 1);
// 打上懶標
if(lazy_seted[p]){
lazy_set[p] += val;
}
else{
lazy_add[p] += val;
}
return;
}
push_down(p, s, t);
int mid = s + (t - s) / 2;
int left_child = 2 * p;
int right_child = 2 * p + 1;
if (L <= mid) {
update_range_add(left_child, s, mid, L, R, val);
}
if (R > mid) {
update_range_add(right_child, mid + 1, t, L, R, val);
}
push_up(p);
}
void update_range_set(int p, int s, int t, int L, int R, long long val) {
if (L <= s && t <= R) {
tree[p] = val * (t - s + 1);
// 打上懶標
lazy_set[p] = val;
lazy_seted[p] = true;
lazy_add[p] = 0;
return;
}
push_down(p, s, t);
int mid = s + (t - s) / 2;
int left_child = 2 * p;
int right_child = 2 * p + 1;
if (L <= mid) {
update_range_set(left_child, s, mid, L, R, val);
}
if (R > mid) {
update_range_set(right_child, mid + 1, t, L, R, val);
}
push_up(p);
}
long long query_range(int node, int start, int end, int L, int R) {
if (L <= start && end <= R) {
return tree[node];
}
push_down(node, start, end);
int mid = start + (end - start) / 2;
int left_child = 2 * node;
int right_child = 2 * node + 1;
long long sum = 0;
if (L <= mid) {
sum += query_range(left_child, start, mid, L, R);
}
if (R > mid) {
sum += query_range(right_child, mid + 1, end, L, R);
}
return sum;
}
void build(int s, int t, int p) {
if (s == t) {
tree[p] = a[s];
return;
}
int m = s + ((t - s) >> 1);
build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[(p * 2) + 1];
}
int main()
{
cin.sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
for (int i = 1 ; i <= n ; i++) {
cin >> a[i];
}
build(1, n, 1);
int m;
while(q--){
cin >> m;
if(m == 1){
int l, r, v;
cin >> l >> r >> v;
update_range_add(1, 1, n, l, r, v);
}
else if(m == 2){
int l, r, v;
cin >> l >> r >> v;
update_range_set(1, 1, n, l, r, v);
}
else{
int l, r;
cin >> l >> r;
cout << query_range(1, 1, n, l, r) << '\n';
}
}
return 0;
}