数据结构 —— 线段树
【概述】
線段樹是一種二叉搜索樹,其存儲(chǔ)的是一個(gè)區(qū)間的信息,每個(gè)結(jié)點(diǎn)以結(jié)構(gòu)體的形式去存儲(chǔ),每個(gè)結(jié)構(gòu)體包含三個(gè)元素:區(qū)間左端點(diǎn)、區(qū)間有端點(diǎn)、該區(qū)間要維護(hù)的信息(視實(shí)際情況而定),其基本思想是分治的思想。
其特點(diǎn)是:
- 每個(gè)節(jié)點(diǎn)的左孩子區(qū)間范圍為 [l,mid],右孩子為 [mid+1,r]
- 對(duì)于結(jié)點(diǎn) k,左孩子結(jié)點(diǎn)為 2*k,右孩子為 2*k+1,符合完全二叉樹的性質(zhì)
線段樹一般結(jié)構(gòu)如圖:
【基礎(chǔ)操作實(shí)現(xiàn)】
線段樹的基礎(chǔ)操作主要有 5 個(gè):建樹、單點(diǎn)查詢、單點(diǎn)修改、區(qū)間查詢、區(qū)間修改
結(jié)點(diǎn):
struct node{int l,r;//區(qū)間左右端點(diǎn)int w;//區(qū)間和 }tree[4*n+1];//樹開4倍空間。?以下的實(shí)現(xiàn)均以求區(qū)間和為例
1.建樹
1)思路
- 對(duì)于二分到的每一個(gè)結(jié)點(diǎn),給它的左右端點(diǎn)確定范圍
- 如果是葉子節(jié)點(diǎn),存儲(chǔ)要維護(hù)的信息
- 狀態(tài)合并
2)實(shí)現(xiàn)
void build(int l,int r,int k){tree[k].l=l;tree[k].r=r;if(l==r){//葉子節(jié)點(diǎn) scanf("%d",&tree[k].w);return; }int mid=(l+r)/2;buildTree(l,mid,k*2);//左孩子 buildTree(mid+1,r,k*2+1);//右孩子tree[k].w=tree[k*2].w+tree[k*2+1].w;//狀態(tài)合并,此結(jié)點(diǎn)的w=兩個(gè)孩子的w和 }2.單點(diǎn)查詢
1)思路
單點(diǎn)查詢即查詢一個(gè)點(diǎn)的狀態(tài),其查詢方法與二分查詢法基本一致。
若當(dāng)前枚舉的點(diǎn)左右端點(diǎn)相等,即為葉節(jié)點(diǎn)時(shí),就是最終的目標(biāo)節(jié)點(diǎn)。
若當(dāng)前枚舉的點(diǎn)左右端點(diǎn)不等,設(shè)查詢位置為 x,當(dāng)前結(jié)點(diǎn)區(qū)間范圍為?l、r,中點(diǎn)為 mid,則若?x<=mid,則遞歸它的左孩子,否則遞歸它的右孩子。
2)實(shí)現(xiàn)
void queryNode(int k){if(tree[k].l==tree[k].r){//當(dāng)前結(jié)點(diǎn)的左右端點(diǎn)相等,為葉子節(jié)點(diǎn),是最終答案 ans=tree[k].w;return;}int mid=(tree[k].l+tree[k].r)/2;if(x<=mid)//目標(biāo)位置比中點(diǎn)靠左,就遞歸左孩子 queryNode(k*2);else//反之,遞歸右孩子 queryNode(k*2+1); }3.單點(diǎn)修改
1)思路
單點(diǎn)修改即更改某一個(gè)點(diǎn)的狀態(tài),對(duì)第 x 個(gè)數(shù)加上 y,其基本思想是結(jié)合單點(diǎn)查詢的原理,找到 x 的位置,然后根據(jù)建樹狀態(tài)合并的原理,修改每個(gè)結(jié)點(diǎn)的狀態(tài)。
2)實(shí)現(xiàn)
void updateNode(int k){if(tree[k].l==tree[k].r){//找到目標(biāo)位置 tree[k].w+=y;return;}int mid=(tree[k].l+tree[k].r)/2;if(x<=mid)//目標(biāo)位置比中點(diǎn)靠左,就遞歸左孩子updateNode(k*2);else//反之,遞歸右孩子updateNode(k*2+1);tree[k].w=tree[k*2].w+tree[k*2+1].w;//所有包含結(jié)點(diǎn)k的結(jié)點(diǎn)狀態(tài)更新 }4.區(qū)間查詢
1)思路
區(qū)間查詢,即查詢一段區(qū)間的狀態(tài)
2)實(shí)現(xiàn)
void queryInterval(int k,int x,int y){if(tree[k].l>=x&&tree[k].r<=y){ans+=tree[k].w;return;}int mid=(tree[k].l+tree[k].r)/2;if(x<=mid) queryInterval(k*2,x,y);if(y>mid) queryInterval(k*2+1,x,y); }5.區(qū)間修改
1)思路
區(qū)間修改即修改一段連續(xù)區(qū)間的值,給區(qū)間 [a,b] 的每個(gè)數(shù)都加 x
線段樹更新樹時(shí),為了避免更新而導(dǎo)致超時(shí)問題,因此每次修改只修改相對(duì)應(yīng)的區(qū)間,然后記錄一個(gè)延遲標(biāo)記,其作用是:存儲(chǔ)到這個(gè)節(jié)點(diǎn)的修改信息,暫時(shí)不把修改信息傳到子節(jié)點(diǎn)。簡(jiǎn)單來說,每次更新的時(shí)候不要更新到底,用延遲標(biāo)記使得更新延遲到下次需要更新 or 詢問的時(shí)候。
下次更新或者查詢的時(shí)候,如果查到該節(jié)點(diǎn),就把延遲標(biāo)記進(jìn)行下傳,將值加到他的子節(jié)點(diǎn)上去,同時(shí)將延遲標(biāo)記變?yōu)?0,避免下次重復(fù)更新。這樣只更新到查詢的子區(qū)間,不需要再往下找了,極大的降低了時(shí)間復(fù)雜度。
以下圖為例,一開始對(duì)區(qū)間 [1,4] 每個(gè)值都 +3,只有當(dāng)需要對(duì) [3,4] 區(qū)間查詢時(shí),才對(duì)下面的區(qū)間進(jìn)行更新,其他區(qū)間無(wú)需更新。
具體操作:
- 原結(jié)構(gòu)體中增加新的變量,存儲(chǔ)這個(gè)標(biāo)記
- 遞歸到這個(gè)節(jié)點(diǎn)時(shí),只更新這個(gè)節(jié)點(diǎn)的狀態(tài),并把當(dāng)前的更改值累積到標(biāo)記中
- 當(dāng)需要遞歸這個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)時(shí),標(biāo)記下傳給子節(jié)點(diǎn),此時(shí)不必是哪個(gè)子節(jié)點(diǎn),兩個(gè)都傳下去
下傳操作的原理:
- 當(dāng)前節(jié)點(diǎn)的標(biāo)記累積到子節(jié)點(diǎn)的標(biāo)記中
- 修改子節(jié)點(diǎn)狀態(tài),在當(dāng)前的求和實(shí)例中,即原狀態(tài)+子節(jié)點(diǎn)區(qū)間點(diǎn)的個(gè)數(shù)*父節(jié)點(diǎn)傳下來的標(biāo)記
- 父節(jié)點(diǎn)標(biāo)記清 0
2)實(shí)現(xiàn)
標(biāo)記下傳:
void pushDown(int k){tree[k*2].f+=tree[k].f;//左孩子更新延遲標(biāo)記tree[k*2+1].f+=tree[k].f;//右孩子更新延遲標(biāo)記tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);//左孩子狀態(tài)更新tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);//右孩子狀態(tài)更新tree[k].f=0;//當(dāng)前延遲標(biāo)記清零 }區(qū)間修改:
void updateInterval(int k,int x,int y){if(tree[k].l>=x&&tree[k].r<=y){//當(dāng)前區(qū)間全部對(duì)要修改的區(qū)間有用tree[k].w+=(tree[k].r-tree[k].l+1)*x;//(r-1)+1區(qū)間點(diǎn)的總數(shù)tree[k].f+=x;return;}if(tree[k].f)//標(biāo)記下傳。只有不滿足上面的if條件才執(zhí)行,所以一定會(huì)用到當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn) pushDown(k);int mid=(tree[k].l+tree[k].r)/2;if(x<=mid) updateInterval(k*2,x,y);if(y>mid) updateInterval(k*2+1,x,y);tree[k].w=tree[k*2].w+tree[k*2+1].w;//更改區(qū)間狀態(tài) }【模版】
1.單點(diǎn)更新+區(qū)間查詢
以求和為例,具體情況根據(jù)題意
struct Node{int l,r;//左右區(qū)間int sum;//區(qū)間和 } tree[N*4]; int a[N]; void pushUp(int i){//維護(hù)子結(jié)點(diǎn)tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; } void build(int i,int l,int r){ //建樹tree[i].l=l;tree[i].r=r;if(l==r){//葉節(jié)點(diǎn)tree[i].sum=a[l];//邊輸入邊建樹//scanf("%d",&a[i]);return;}int mid=(l+r)>>1;build(i*2,l,mid);//結(jié)點(diǎn)的左兒子build(i*2+1,mid+1,r);//結(jié)點(diǎn)的右兒子pushUp(i); }//對(duì)id號(hào)點(diǎn)進(jìn)行修改 void update(int i,int id,int val){//線段樹單點(diǎn)修改if(tree[i].l==tree[i].r){tree[i].sum+=val;return;}int mid=(tree[i].l+tree[i].r)/2;if(id<=mid)update(i*2,id,val);if(id>mid)update(i*2+1,id,val);pushUp(i); }int query(int i,int ql,int qr){//線段樹區(qū)間查詢if(ql<=tree[i].l&&qr>=tree[i].r)//當(dāng)前區(qū)間在目標(biāo)區(qū)間內(nèi)return tree[i].sum;int mid=(tree[i].l+tree[i].r)/2;int res=0;if(ql<=mid)res+=query(i*2,ql,qr);if(qr>mid)res+=query(i*2+1,ql,qr);return res; }int main(){int n,m;cin>>n;for(int i=1;i<=n;i++)//初始值cin>>a[i];build(1,1,n);//先輸入再建樹cin>>m;//m組詢問while(m--){int p;cin>>p;if(p==1){//單點(diǎn)更新int id,val;cin>>id>>val;update(1,id,val);}else if(p==2){//區(qū)間查詢int a,b;cin>>a>>b;cout<<query(1,a,b)<<endl;}}return 0; }2.區(qū)間更新+區(qū)間查詢
struct Node{int l,r;//左右區(qū)間int sum;//區(qū)間和int maxx,minn;//區(qū)間最值int lazyAdd;//區(qū)間增值時(shí)的延遲標(biāo)記int lazySet;//區(qū)間賦值時(shí)的延遲標(biāo)記 }tree[N*4]; int a[N]; int resSum,resMax,resMin;//存儲(chǔ)結(jié)果 void pushDown(int i){//標(biāo)記下傳if(tree[i].lazySet!=-1){tree[i*2].lazySet=tree[i*2+1].lazySet=tree[i].lazySet;tree[i*2].lazyAdd=tree[i*2+1].lazyAdd=0;tree[i*2].minn=tree[i*2+1].minn=tree[i].lazySet;tree[i*2].maxx=tree[i*2+1].maxx=tree[i].lazySet;tree[i*2].sum=(tree[i*2].r-tree[i*2].l+1)*tree[i].lazySet;tree[i*2+1].sum=(tree[i*2+1].r-tree[i*2+1].l+1)*tree[i].lazySet;tree[i].lazySet=-1;}///左子節(jié)點(diǎn)tree[i*2].lazyAdd+=tree[i].lazyAdd;//打上延遲標(biāo)記tree[i*2].minn+=tree[i].lazyAdd;//更新tree[i*2].maxx+=tree[i].lazyAdd;//更新tree[i*2].sum+=tree[i].lazyAdd*(tree[i*2].r-tree[i*2].l+1);//更新///右子節(jié)點(diǎn)tree[i*2+1].lazyAdd+=tree[i].lazyAdd;//打上延遲標(biāo)記tree[i*2+1].minn+=tree[i].lazyAdd;//更新tree[i*2+1].maxx+=tree[i].lazyAdd;//更新tree[i*2+1].sum+=tree[i].lazyAdd*(tree[i*2+1].r-tree[i*2+1].l+1); //更新tree[i].lazyAdd=0;//清除標(biāo)記 }void pushUp(int i){//維護(hù)子節(jié)點(diǎn)tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;tree[i].maxx=max(tree[i*2].maxx,tree[i*2+1].maxx);tree[i].minn=min(tree[i*2].minn,tree[i*2+1].minn); }void build(int i,int l,int r){//建樹tree[i].l=l;tree[i].r=r;tree[i].lazyAdd=0;tree[i].lazySet=-1;if(l==r){//葉結(jié)點(diǎn)tree[i].sum=a[l];tree[i].maxx=a[l];tree[i].minn=a[l];return;}int mid=(l+r)>>1;build(i*2,l,mid);//結(jié)點(diǎn)左兒子build(i*2+1,mid+1,r);//結(jié)點(diǎn)右兒子pushUp(i); }void updateSet(int i,int ql,int qr,int val){//區(qū)間修改,整體賦值為valif(tree[i].l>=ql && tree[i].r<=qr){tree[i].sum=val*(tree[i].r-tree[i].l+1);tree[i].minn=val;tree[i].maxx=val;tree[i].lazySet=val;tree[i].lazyAdd=0;return;}pushDown(i);//標(biāo)記下傳int mid=(tree[i].l+tree[i].r)/2;if(ql<=mid)updateSet(i*2,ql,qr,val);if(qr>mid)updateSet(i*2+1,ql,qr,val);pushUp(i); }void updateAdd(int i,int ql,int qr,int val){//區(qū)間修改,整體+valif(tree[i].l>=ql&&tree[i].r<=qr){tree[i].sum+=val*(tree[i].r-tree[i].l+1);tree[i].minn+=val;tree[i].maxx+=val;tree[i].lazyAdd += val;return;}pushDown(i);//標(biāo)記下傳int mid=(tree[i].l+tree[i].r)/2;if(ql<=mid)updateAdd(i*2,ql,qr,val);if(qr>mid)updateAdd(i*2+1,ql,qr,val);pushUp(i); }void query(int i,int ql,int qr){//區(qū)間查詢if(ql<=tree[i].l && tree[i].r<=qr){resSum+=tree[i].sum;resMax=max(resMax,tree[i].maxx);resMin=min(resMin,tree[i].minn);return ;}pushDown(i);int mid=(tree[i].l+tree[i].r)/2;if(ql<=mid)query(i*2,ql,qr);if(qr>mid)query(i*2+1,ql,qr);pushUp(i); }int main(){int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];build(1,1,n);int m;cin>>m;while(m--){int p;cin>>p;if(p==1){//區(qū)間整體賦值int a,b;//區(qū)間int val;//值scanf("%d%d%d",&a,&b,&val);updateSet(1,a,b,val);}else if(p==2){//區(qū)間整體加值int a,b;//區(qū)間int val;//值scanf("%d%d%d",&a,&b,&val);updateAdd(1,a,b,val);}else if(p==3){//區(qū)間查詢int a,b;cin>>a>>b;resSum=0,resMax=-INF,resMin=INF;query(1,a,b);cout<<"Sum="<<resSum<<endl;cout<<"Max="<<resMax<<endl;cout<<"Min="<<resMin<<endl;}}return 0; }【例題】
- I Hate It(HDU-1754)(求最值+單點(diǎn)更新):點(diǎn)擊這里
- A Simple Problemwith Integers(POJ-3468)(區(qū)間和+區(qū)間更新):點(diǎn)擊這里
- Naive Operations(HDU-6315)(維護(hù)技巧+區(qū)間更新):點(diǎn)擊這里
- Can you answer these queries?(HDU-4027)(開方操作+區(qū)間更新):點(diǎn)擊這里
- Tunnel Warfare(HDU-1540)(區(qū)間合并):點(diǎn)擊這里
- 肥豬(2019牛客寒假算法基礎(chǔ)集訓(xùn)營(yíng) Day6-H)(區(qū)間最小值):點(diǎn)擊這里
總結(jié)
以上是生活随笔為你收集整理的数据结构 —— 线段树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TDL(HDU-6641)
- 下一篇: 小Z的袜子(BZOJ-2038)