English Name : Segment Tree 顧名思義 : 該數據結構由兩個重要的東西組成 : 線段,樹,連起來就是在樹上的線段。 想一下,線段有啥特徵 ? 不就是兩個端點中間一條線嗎,哈哈,也能夠這麼理解,但這樣是否是稍微難聽呀,因此 咱們用一個華麗的詞語來描述這個兩點之間的一條線,這個詞語就是不知道哪一個先知發 明的,就是 -- 區間。 因此咱們就可猜測到,因此線段樹必定是用來處理區間問題的。
展現一個區間 1 - 10 的一顆線段樹,就是這麼個樹東西。
一、線段樹的每一個節點都表明一個區間 二、線段樹具備惟一的根節點,表明的區間的整個統計範圍,[1,N] 三、線段樹的每一個葉節點都表明一個長度爲 1 的元區間 [x,x],也就是咱們原數組中每一個值,原數組中有幾個值 就有多少個葉子節點(能夠參照上圖瞭解一下)。 四、對於每一個內部節點 [l,r],它的左子節點是 [l,mid],右子節點是 [mid + 1,r],mid = l + r >> 1(向下取整)
一、單點查詢(查詢某個位置上的值是多少) 二、單點修改(修改某個位置上的值) 三、區間查詢(查詢某個區間的 和、最大值、最小值、最大公約數、and so on) 四、區間修改(修改某個區間的值, eg:讓某個區間都 + 一個數、and so on)
一、結構體空間必定要開 4 倍,必定要記得看 4 倍(看上面這棵樹,按節點編號咱們能夠看到一共有 25 個節點,但算上空餘的位置呢?) 會發現有 31 個節點,能夠本身數一下,因此咱們要開原數組的 4 倍,避免出現數組越界,非法訪問的狀況(段錯誤)。 二、區間判斷的時候必定不要寫反(下面寫的時候就知道了,這個坑讓我 Debug 了一個多小時) 三、沒事多打打,模板,就當練手速了。
struct node { LL l,r; LL sum; // 看須要向父節點傳送什麼 } tr[maxn << 2];
void pushup(LL u) { tr[u].sum = gcd(tr[u << 1].sum,tr[u << 1 | 1].sum); return ; } void build(LL u,LL l,LL r) { tr[u].l = l,tr[u].r = r; // 初始化(節點 u 表明區間 [l,r]) if(l == r) { tr[u].sum = b[l]; // 遞歸到葉節點賦初值 return ; } LL mid = l + r >> 1; // 折半 build(u << 1,l,mid); // 向左子節點遞歸 build(u << 1 | 1,mid + 1,r); // 向右子節點遞歸 pushup(u); // 從下往上傳遞信息 return; }
void update(LL u,LL x,LL v) { if(tr[u].l == tr[u].r) { // 找到葉節點 tr[u].sum += v; // 在某個位置加上一個數 return ; } LL mid = tr[u].l + tr[u].r >> 1; if(x <= mid) update(u << 1,x,v); // x 屬於左半區間 else update(u << 1 | 1,x,v); // x 屬於右半區間 pushup(u); // 從下向上更新信息 return ; }
一、若 [l,r] 徹底覆蓋了當前節點表明的區間,則當即回溯。 二、若左子節點與 [l,r] 有重疊部分,則遞歸訪問左子節點。 三、若右子節點與 [l,r] 有重疊部分,則遞歸訪問右子節點。
LL query(int u,int l,int r) { if(tr[u].l >= l && tr[u].r <= r) { // 徹底包含 return tr[u].sum; } int mid = tr[u].l + tr[u].r >> 1; LL sum = 0; if(l <= mid) sum += query(u << 1,l,r); if(r > mid) sum += query(u << 1 | 1,l,r); return sum; }
上述就是線段樹的基本操做,基本上都是圍繞單點問題進行操做,若是要涉及到複雜的區間操做,
例如 : 給區間 [l,r] 每一個數都 + d
這時若是還用上述操做,咱們就須要進行 l - r + 1 次操做,若是有屢次這樣的操做,顯然時間
複雜度會很高,這時候咱們應該選擇什麼樣的方法來下降時間複雜度呢 ?node
簡單一點來講就是,減小重複的操做,若是說咱們操做的每個數都在一個區間範圍內,那麼 咱們就能夠直接處理這個區間,不須要再一個一個處理,好比上面的給區間的每個數 + d; 假設說咱們已經知道 [l,r] 徹底包含一個區間 [x,y],也就是說 區間[x,y]是 [l,r]的 一個子區間,那麼這個時候咱們是否是直接能夠計算出 [x,y] 這個區間 都 + d 後的值是 多少, (x - y + 1) * d(假設是求和的話),這樣咱們就能夠再也不用去一個一個加,而後 再合併了,咱們知道有這樣的區間後,怎麼用呢?這時候就須要進行標記一下,便於咱們知道 這個地方有一個區間能夠直接處理,不須要再麻煩着向下繼續去處理了,是否是很懶,哈哈。
/* 懶標記的含義 : 該節點曾經被修改,但其子節點還沒有被更新。 在後續的指令中,咱們須要從某個節點向下遞歸時,檢查該節點是否具備標記,如有標記,就根據 標記信息更新 該節點 的兩個子節點,同時爲該節點的兩個子節點增長標記,而後清楚 p 的標記。 */ void pushdown(int u) { if(tr[u].lazy) { // 節點 u 有標記 tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1); // 更新左子節點信息 tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1); // 更新右子節點 tr[u << 1].lazy += tr[u].lazy; // 給左子節點打延遲標記 tr[u << 1 | 1].lazy += tr[u].lazy; // 給右子節點打延遲標記 tr[u].lazy = 0; // 清楚父節點的延遲標記(這點很重要) } return ; }
// Build 不變 // Update void modify(int u,int l,int r,int x) { if(tr[u].l >= l && tr[u].r <= r) { // 徹底覆蓋 tr[u].sum += (tr[u].r- tr[u].l + 1) * x; // 更新節點信息 tr[u].lazy += x; // 給節點打延遲標記 return ; } pushdown(u); // 下傳延遲標記 int mid = tr[u].l + tr[u].r >> 1; if(l <= mid) modify(u << 1,l,r,x); if(r > mid) modify(u << 1 | 1,l,r,x); pushup(u); return ; } // Query LL query(int u,int l,int r) { if(tr[u].l >= l && tr[u].r <= r) { return tr[u].sum; } pushdown(u); // 同上 int mid = tr[u].l + tr[u].r >> 1; LL sum = 0; if(l <= mid) sum += query(u << 1,l,r); if(r > mid) sum += query(u << 1 | 1,l,r); return sum; }
線段樹的操做基本上就這些,哈哈,實際上本身就瞭解這麼多,並且是最近有幾場比賽遇見挺多的,就學了一下, 主要是手得多動動,有時候考察得仍是比較複雜得,先把這些基礎得模板搞懂吧。
一、一個簡單的整數問題ios
#include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; typedef long long LL; struct node { int l,r; LL sum,lazy; }tr[maxn << 2]; int a[maxn]; int n,m; int l,r; int main(void) { void build(int u,int l,int r); void modify(int u,int l,int r,int x); LL query(int u,int l,int r); scanf("%d%d",&n,&m); for(int i = 1; i <= n; i ++) { scanf("%d",&a[i]); } build(1,1,n); while(m --) { char ch; cin >> ch; if(ch == 'Q') { scanf("%d",&l); printf("%lld\n",query(1,1,l) - query(1,1,l - 1)); } else { int value; scanf("%d%d%d",&l,&r,&value); modify(1,l,r,value); } } return 0; } void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; return ; } void pushdown(int u) { if(tr[u].lazy) { tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1); tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1); tr[u << 1].lazy += tr[u].lazy; tr[u << 1 | 1].lazy += tr[u].lazy; tr[u].lazy = 0; } return ; } void build(int u,int l,int r) { tr[u].l = l,tr[u].r = r; if(l == r) { tr[u].sum = a[l]; return ; } int mid = l + r >> 1; build(u << 1,l,mid); build(u << 1 | 1,mid + 1,r); pushup(u); return ; } void modify(int u,int l,int r,int x) { if(tr[u].l >= l && tr[u].r <= r) { tr[u].sum += (tr[u].r- tr[u].l + 1) * x; tr[u].lazy += x; return ; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if(l <= mid) modify(u << 1,l,r,x); if(r > mid) modify(u << 1 | 1,l,r,x); pushup(u); return ; } LL query(int u,int l,int r) { if(tr[u].l >= l && tr[u].r <= r) { return tr[u].sum; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; LL sum = 0; if(l <= mid) sum += query(u << 1,l,r); if(r > mid) sum += query(u << 1 | 1,l,r); return sum; }
二、一個簡單的整數問題2數組
#include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; typedef long long LL; struct node { int l,r; LL sum,lazy; }tr[maxn << 2]; int a[maxn]; int n,m; int l,r; int main(void) { void build(int u,int l,int r); void modify(int u,int l,int r,int x); LL query(int u,int l,int r); scanf("%d%d",&n,&m); for(int i = 1; i <= n; i ++) { scanf("%d",&a[i]); } build(1,1,n); while(m --) { char ch; cin >> ch; if(ch == 'Q') { scanf("%d%d",&l,&r); printf("%lld\n",query(1,l,r) ); } else { int value; scanf("%d%d%d",&l,&r,&value); modify(1,l,r,value); } } return 0; } void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; return ; } void pushdown(int u) { if(tr[u].lazy) { tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1); tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1); tr[u << 1].lazy += tr[u].lazy; tr[u << 1 | 1].lazy += tr[u].lazy; tr[u].lazy = 0; } return ; } void build(int u,int l,int r) { tr[u].l = l,tr[u].r = r; if(l == r) { tr[u].sum = a[l]; return ; } int mid = l + r >> 1; build(u << 1,l,mid); build(u << 1 | 1,mid + 1,r); pushup(u); return ; } void modify(int u,int l,int r,int x) { if(tr[u].l >= l && tr[u].r <= r) { tr[u].sum += (tr[u].r- tr[u].l + 1) * x; tr[u].lazy += x; return ; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if(l <= mid) modify(u << 1,l,r,x); if(r > mid) modify(u << 1 | 1,l,r,x); pushup(u); return ; } LL query(int u,int l,int r) { if(tr[u].l >= l && tr[u].r <= r) { return tr[u].sum; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; LL sum = 0; if(l <= mid) sum += query(u << 1,l,r); if(r > mid) sum += query(u << 1 | 1,l,r); return sum; } 做者:Eureka 連接:https://www.acwing.com/activity/content/code/content/198208/ 來源:AcWing 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。