線段樹

優化序列的區間詢問

給一序列,支援求區間和,和修改每一個元素的值

暴力:修改\(O(1)\),查詢\(O(n)\)

 

前綴和:修改\(O(n)\),查詢\(O(1)\)

線段樹:修改\(O(\log n)\),查詢\(O(\log n)\)

把區間一直分一半 (二元樹)

總共log n層

每一個節點存那個區間的答案

假設想查詢3~8的和

圈起來的節點答案加起來

aka從根結點遞迴把包含到的區間加起來

詢問3~8

[3,8]不貼合[0,8]

[3,8]有覆蓋到[0,4],遞迴到那

詢問3~4 (查詢區間不能超過目前節點)

[3,4]不貼合[0,4]

[3,4]沒有覆蓋到[0,2],但[4,4]有,遞迴到那

詢問3~4

[3,4]貼合[3,4],回傳此節點答案

詢問3~4

詢問3~8

[3,8]不貼合[0,8]

[3,8]有覆蓋到[5,8],遞迴到那

詢問5~8

[5,8]貼合[5,8]

回傳此節點答案

詢問5~8

[5,8]貼合[5,8]

回傳此節點答案

詢問複雜度\(O(\log n)\)

最多log n層,而且遞迴兩個分枝的次數不會太多

單點修改

如要更新位置i,只需一路遞迴到[i,i]的節點,並且往上一路更新

更新3成2

更新3成2

更新3成2

更新3成2

2

更新3成2

2

11

更新3成2

2

11

22

更新3成2

2

11

22

31

#include <bits/stdc++.h>
using namespace std;

int tree[400010], val[100010];

void build(int v, int l, int r){
    if(l==r){
        tree[v]=val[l];
        return;
    }
    int mid=(l+r)>>1;
    build(2*v, l, mid);
    build(2*v+1, mid+1, r);
    tree[v]=tree[2*v]+tree[2*v+1];
}

void upd(int v, int l, int r, int pos, int x){
    if(l==r){
        tree[v]=x;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) upd(2*v, l, mid, pos, x);
    else upd(2*v+1, mid+1, r, pos, x);
    tree[v]=tree[2*v]+tree[2*v+1];
}

int qry(int v, int l, int r, int ql, int qr){
    if(l==ql && r==qr) return tree[v];
    int mid=(l+r)>>1;
    if(qr<=mid) return qry(2*v, l, mid, ql, qr);
    else if(ql>mid) return qry(2*v+1, mid+1, r, ql, qr);
    else return qry(2*v, l, mid, ql, mid)+qry(2*v+1, mid+1, r, mid+1, qr);
}

實作細節

  • 陣列要開4*n
  • 根節點編號1
  • 如果目前節點是v,左節點是2*v,右是2*v+1

例題

區間最大連續和

線段樹節點存三個資訊:

  • lmx=從左邊開始最大連續和
  • rmx=從右邊開始最大連續和
  • mx=介在中間的最大連續和

節點合併:

  • mx=max(左mx,右mx, 左rmx+右lmx)
  • lmx=max(左lmx, 左區間和+右lmx)
  • rmx=max(rmx, 左rmx+右區間和)
#include <bits/stdc++.h>

using namespace std;

struct nd{
    ll lmx, rmx, mx, sum;
};

ll val[200010];
nd tree[800010];

nd pull(nd &a, nd &b){
    nd tmp;
    tmp.mx=max(a.mx, b.mx);
    tmp.mx=max(tmp.mx, a.rmx+b.lmx);
    tmp.lmx=max(a.lmx, a.sum+b.lmx);
    tmp.rmx=max(b.rmx, b.sum+a.rmx);
    return tmp;
}

void build(int v, int l, int r){
   if(l==r){
       ll tmp=max(0, val[l]);
       tree[v]={tmp, tmp, tmp, val[l]};
       return;
   } 
   int mid=(l+r)>>1;
   build(2*v, l, mid);
   build(2*v+1, mid+1, r);
   tree[v]=pull(tree[2*v], tree[2*v+1]);
}

void upd(int v, int l, int r, int pos, ll x){
    if(l==r){
        ll tmp=max(0, x);
        tree[v]={tmp, tmp, tmp, x};
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) upd(2*v, l, mid, pos, x);
    else upd(2*v+1, mid+1, r, pos, x);
    tree[v]=pull(tree[2*v], tree[2*v+1]);
}

nd qry(int v, int l, int r, int ql, int qr){
    if(l==qr && r==qr) return tree[v];
    int mid=(l+r)>>1;
    if(qr<=mid) return qry(2*v, l, mid, ql, qr);
    else if(ql>mid) return qry(2*v+1, mid+1, r, ql, qr);
    return pull(qry(2*v, l, mid, ql, mid), qry(2*v+1, mid+1, r, mid+1, qr));
}

懶標

給一序列,支援求區間和,和對一個區間的數字都加上一個值

還記得差分?

\(O(1)\)修改

\(O(n)\)查詢

懶標(lazy tag) - 在一個節點標記那個區間都要被加上某個值

加值時如果加值區間和節點區間一樣,就在那裡打懶標(標記區間+某值)

之後如果遞迴時需要遞迴到更深處,就要把懶標的資訊推到左右子樹

0

0

0

0

0

0

0

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

0

0

0

0

0

0

0

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

0

0

0

0

0

14

0

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

0

0

0

0

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

0

0

0

0

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

0

0

0

0

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

0

0

0

14

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

0

14

0

14

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

28

14

0

14

0

14

14

0

0

0

將3~6加7

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

複雜度\(O(\log n)\)

跟區間查詢一樣

28

14

0

14

0

14

14

0

0

0

查詢3~6的和

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

28

14

0

14

0

14

14

0

0

0

查詢3~6的和

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

28

14

0

14

0

14

14

0

0

0

查詢3~6的和

0

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

28

14

0

14

7

14

14

0

0

0

查詢3~6的和

7

0

0

0

0

    1              2              3             4           5                6              7            8

tag=7

tag=7

tag=7

\(O(\log n)\)

#include <bits/stdc++.h>
#define EB emplace_back
#define vci vector<int>
#define PII pair<int,int>
#define F first
#define S second
#define rep(X, a,b) for(int X=a;X<b;++X)
#define ALL(a) (a).begin(), (a).end()
#define SZ(a) (int)(a).size()
#define NL "\n"
#define LL long long

using namespace std;

LL tree[5*500000], num[500010], tag[5*500000]={0};

void push(int v, int l, int r){
	int mid=(l+r)>>1;
	tag[2*v]+=tag[v];
	tag[2*v+1]+=tag[v];
	tree[2*v]+=(mid-l+1)*tag[v];
	tree[2*v+1]+=(r-mid)*tag[v];
	tag[v]=0;
}

void build(int v, int l, int r){
	if(l==r) tree[v]=num[l];
	else{
		int mid=(l+r)>>1;
		build(2*v, l, mid);
		build(2*v+1, mid+1, r);
		tree[v]=tree[2*v]+tree[2*v+1];
	}
}

void modify(int v, int l, int r, int ul, int ur, LL x){
	if(l==ul && r==ur){
		tag[v]+=x;
		tree[v]+=(r-l+1)*x;
		return;
	}
	if(tag[v]) push(v, l, r);
	int mid=(l+r)>>1;
	if(ur<=mid) modify(2*v, l, mid, ul, ur, x);
	else if(ul>mid) modify(2*v+1, mid+1, r, ul, ur, x);
	else{
		modify(2*v, l, mid, ul, mid, x);
		modify(2*v+1, mid+1, r, mid+1, ur, x);
	}
	tree[v]=tree[2*v]+tree[2*v+1];
}

LL query(int v, int l, int r, int ql, int qr){
	if(l==ql && r==qr) return tree[v];
	if(tag[v]) push(v, l, r);
	int mid=(l+r)>>1;
	if(qr<=mid) return query(2*v, l, mid, ql, qr);
	else if(ql>mid) return query(2*v+1, mid+1, r, ql, qr);
	return query(2*v, l, mid, ql, mid)+query(2*v+1, mid+1, r, mid+1, qr);
}

持久化線段樹

時間線段樹

線段樹

By alan lai

線段樹

  • 174