你以為我是樹狀陣列?
其實我是線段樹噠!
這兩個東西作為同是資料結構又同是樹狀的東西,很讓人迷糊,
你看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和阿...