真就全網最詳基礎線段樹

2022-04-12 02:44:45 字數 4505 閱讀 1885

你以為我是樹狀陣列?

其實我是線段樹噠!

這兩個東西作為同是資料結構又同是樹狀的東西,很讓人迷糊,

你看ta們的題號:

然而看**長度就容易區分多了

那麼ta們之間到底有什麼聯絡(廢話,都是樹狀唄)與不同呢(名稱)

先講完線段樹再歸納整理吧

線段樹可維護的東西可比上面的辣雞st和樹狀陣列多(好像後面就不用歸納整理了...)

ta是這樣一種結構:

當然這是將區間以數字形式表現,也可以直觀表現,像這樣:

(p.s. 這裡的"root"點並不是將最頂上的線段一分為二,最上面的線段就是一條線段,也就是說這個"root"點可以忽略)

也是像樹狀陣列一樣的,將大區間拆分為小的,查詢時選取相應區間做指定運算就是了

最垃圾的線段樹支援的操作有:

區間修改,

區間查詢

好像沒了

最垃圾的線段樹可以維護的資訊有:

區間和區間最小值

區間最大值

區間異或值

(好像沒有區間乘積這樣一種毒瘤操作,有了也沒啥意義)

其他總而言之,線段樹就是一種用來對數列進行花式維護的資料結構

具體的,其原理如下:

需要注意,在實現演算法的途中,即建樹過程中,假定當前編號為\(k\),

我們規定乙個非葉節點的左兒子編號為\(k*2\),右兒子編號為\(k*2+1\),

這樣十分方便建樹

4倍,沒別的

p.s.本來想寫關於線段樹3倍空間複雜度證明,但是其中感性理解太多

其實好像線段樹是可以3倍空間的,但是聽說某道題會re所以保險起見開4倍吧...

看完最後一部分再回來想一想吧\(\downarrow\ \downarrow\ \downarrow\)

來看下每個部分的**:

本**維護區間和,有興趣自己練最大值最小值,

\(build\)建造函式

用於建樹,每次傳參傳的是節點編號\(k\),區間左端點\(l\),右端點\(r\)

思路:

void build(int k,int l,int r)int mid=l+r>>1;

build(k<<1,l,mid);

build(k<<1|1,mid+1,r);

sum[k]=sum[k<<1]+sum[k<<1|1];

}

insert修改函式,區間加

區間加上某個值

思路:

void insert(int k,int l,int r,int x,int y,int v)
當然還有演算法是將標記永久化,

個人不喜歡,所以不用,畢竟消除標記方便多種運算的使用,就是同時支援區間加,乘,開方啥的,

於是我們將\(o(nlog_2n)\)的複雜度優化成了...如果不是全都是單點修改的話...嚴格小於\(o(nlog_2n)\)的複雜度

但是這可優化可是很大的(噘嘴)!

這就將最垃圾的線段樹優化成了...

不是特別垃圾的基本線段樹

結構體維護版本:

#include#includeusing namespace std;

#define ci const int &

#define ll long long

const int n=100005;

int n,m;

struct nodend[n<<2];

ll a[n];

inline void build(ci k,ci l,ci r)

int mid=l+r>>1;

build(k<<1,l,mid);

build(k<<1|1,mid+1,r);

nd[k].sum=nd[k<<1].sum+nd[k<<1|1].sum;

}inline void pushdown(ci k,ci l,ci r)

inline void insert(ci k,ci l,ci r,ci x,ci y,const ll &v)

pushdown(k,l,r);

int mid=l+r>>1;

if(x<=mid) insert(k<<1,l,mid,x,y,v);

if(mid>1;

ll ret=0;

if(x<=mid) ret+=find(k<<1,l,mid,x,y);

if(mid陣列維護版本:

#include#includeusing namespace std;

#define ci const int &

#define ll long long

const int n=100005;

int n,m;

ll sum[n<<2];

ll laz[n<<2];

ll a[n];

inline void build(ci k,ci l,ci r)

int mid=l+r>>1;

build(k<<1,l,mid);

build(k<<1|1,mid+1,r);

sum[k]=sum[k<<1]+sum[k<<1|1];

}inline void pushdown(ci k,ci l,ci r)

inline void insert(ci k,ci l,ci r,ci x,ci y,const ll &v)

pushdown(k,l,r);

int mid=l+r>>1;

if(x<=mid) insert(k<<1,l,mid,x,y,v);

if(mid>1;

ll ret=0;

if(x<=mid) ret+=find(k<<1,l,mid,x,y);

if(mid對於部分人來說...好長的模板題呀!!!

顯然你沒敲過樹鏈剖分什麼的...

函式加上\(inline\),\(const\)和&可以更快,但\(const\)僅用於不修改其值的情況,是個常用的卡常技巧,這裡我騷氣地定義成了\(ci\)

同樣,因為計算機喜歡用二進位制,所以位運算顯然要快一些

那麼前面整理的\(st\)表,樹狀陣列,線段樹到底聯絡在哪,區別在哪?

先說\(st\),其由於結構較簡單,只能維護較簡單的資訊,也就求求最大值最小值

那麼樹狀陣列就要稍微好些,可以維護字首和,字首最大最小值,字首積,

但是其缺陷就是在於只能維護"字首",求和海星,然而求積的話,如果乘積較大需要取模,那麼可能會出現這樣一種東西(設查詢區間\(1\sim x\)字首乘積函式值為\(f(x)\),模數為_rqy):

\(\cfrac\)

然而眾所周知,取模是不能除的,除非...你會一種叫逆元的東西...

還有區間最大最小值的問題,這根本沒法求...

使用ta的唯一理由怕就是好寫,**短...

那麼對於線段樹...(壞笑)

幾乎上面有限制的ta都能做

另外還可以求區間異或和,區間這那,超級方便,

對於區間\(k\)小值...想聽聽主席樹嗎...

本部分內容僅對於二分法則為\(mid=\left\lfloor\dfrac \right\rfloor\)的線段樹,實際上不這麼分的也沒幾個...

p.s.這些東西本來都是空間複雜度證明中的內容,懶得證了

易得:對於區間長度為\(2^n,n\in z\),其節點個數是\(2*n-1\),不再議論,

那麼如果區間長度不是\(2\)的整次冪...

對於任意的奇數長度區間,其左子區間一定比右子區間長

原因如下:

對於奇數區間,其右端點可以表示成如下狀態:\(l\)(左端點)\(+len\)(奇數區間長度)\(-1\),

那麼,其劃分的區間就是\([l,\dfrac]\)

以及\([\dfrac +1,l+len-1]\)

化簡:\([l,l+\dfrac]\)

與\([l+\dfrac+1,l+len-1]\),也就是\([l+\dfrac,l+len-1]\)

整理一下兩邊的區間長度可得:

左:\(\dfrac+1\),即為\(\dfrac\)

右:\(len-1-\dfrac\),即為\(\dfrac\)

所以左邊的區間長度實際上要大1,

從而順便我們得出另乙個結論,對於任意乙個區間,其左子區間長度減右子區間長度不超過1.

順便說一句,線段樹也可以動態開點,這樣的話每個節點還需要存左右兒子編號

整這篇部落格時間較久,不如...

點個關注或者硬幣都是可以的

點個贊?

LeetCode 格雷編碼(全網最簡)

格雷編碼是乙個二進位制數字系統,在該系統中,兩個連續的數值僅有乙個位數的差異。給定乙個代表編碼總位數的非負整數 n,列印其格雷編碼序列。格雷編碼序列必須以 0 開頭。示例 1 輸入 2 輸出 0,1,3,2 解釋 00 0 01 1 11 3 10 2 對於給定的 n,其格雷編碼序列並不唯一。例如,...

找遍全網最簡單的使用git

2 進行基礎配置,作為 git 的基礎配置,作用是告訴 git 你是誰,你輸入的資訊將出現在你建立的提交中,使用下面兩條命令 git config global user.name 你的名字或暱稱 git config global user.email 你的郵箱 3.在你自己人員資料夾中執行下面命...

全網最火SpringCloud2020全家桶教程

教程重點講解了springcloud各種元件停止更新進入維護階段後,後續技術元件的公升級和替換策略及方案選型,既有傳統eureka ribbon openfeign hystrix config等技術的公升級講解,又有consul gateway bus stream sleuth zipkin和阿...