1. 程式人生 > 其它 >學習筆記——線段樹

學習筆記——線段樹

線段樹

Question

用一個數據結構維護一個數列,支援:

  • 單點/區間修改
  • 區間求和/最大值/最小值/...

DateStructure:線段樹

整體思想

線段樹就是一大段分成兩小段,對於小段再繼續分割,更新時更新父節點,查詢時合併區間的查詢。

操作方法

單點修改

從最大區間開始一直尋找包含修改點的區間,同時對包含此點區間進行值的修改。

區間查詢

從最大區間開始一直遍歷兩個子區間,每個區間有三種情況:

  1. 與查詢區間完全不相交,此時直接跳出。
  2. 被查詢區間完全包含,此時返回此區間的值。
  3. 1,2都不滿足,此時繼續遍歷兩個子區間。

Lazy Tag

當進行區間修改的時候,如果一次性更新到最底層的節點時,時間就會變成 \(O(n)\)

,穩穩炸飛。

這時候我們就要打“懶標記”了。

當一個區間完全被包含在修改區間內的時候,就給他打上標記,當查詢到它的子區間時再去下傳標記。

Code

單點查詢和更改更簡單一些,會了區間肯定能實現出來,這裡就只粘上區間更改和查詢的板子了。

其實就是懶。

void pushdown(ll p){//下傳標記
	if(!t[p].bj)return;
	t[p*2].bj+=t[p].bj,t[p*2].val+=t[p].bj*(t[p*2].r-t[p*2].l+1);
	t[p*2+1].bj+=t[p].bj,t[p*2+1].val+=t[p].bj*(t[p*2+1].r-t[p*2+1].l+1);
	t[p].bj=0;
	//先打標記,再更新值
}ll build_tree(ll p,ll l,ll r){//
	t[p].l=l,t[p].r=r;
	if(l==r)scanf("%lld",&t[p].val);
	//最底層的點
	else{
		ll mid=(l+r)>>1;
		t[p].val=build_tree(p*2,l,mid)+build_tree(p*2+1,mid+1,r);
		//建兒子
	}return t[p].val;
}void update(ll p,ll l,ll r,ll d){//區間更新
	ll le=t[p].l,ri=t[p].r;
	if(le>r||ri<l)return;
	if(le>=l&&ri<=r)t[p].val+=d*(t[p].r-t[p].l+1),t[p].bj+=d;
	//打上標記
	else{
		pushdown(p),update(p*2,l,r,d),update(p*2+1,l,r,d);
		t[p].val=t[p*2].val+t[p*2+1].val;
		//給兒子下傳之前的標記
	}
}ll query(ll p,ll l,ll r){//查詢
	ll le=t[p].l,ri=t[p].r,ans;
	if(le>r||ri<l)return 0;
	if(le>=l&&ri<=r)return t[p].val;
	pushdown(p);//下傳標記
	return ans=query(p*2,l,r)+query(p*2+1,l,r);
}

Example:山海經

因為教練給我們留的六道題中五道都是裸板子,所以只能直接放毒瘤題了。

Meaning of the Problem

給你一個序列,每次查詢區間 \((x,y)\) 的最大子段和。

Solution

我們要維護一堆東西:

  • 區間和
  • 最大字首和其右端點
  • 最大子段和其左右端點
  • 最大字尾和其左端點

為什麼這麼麻煩呢?

因為我們要通過合併來維護子段和。

  1. 對於一個區間的最大字首和有兩種情況:
    1. 左子區間的最大字首和。
    2. 左子區間和加右子區間的最大字首和。
  2. 對於一個區間的最大字尾和有兩種情況:
    1. 右子區間的最大字尾和。
    2. 右子區間和加左子區間的最大字尾和。
  3. 對於一個區間的最大子段和有三種情況:
    1. 左子區間的最大子段和。
    2. 右子區間的最大子段和。
    3. 左子區間的最大字尾和加右子區間的最大字首和。

只要我們把板子裡的"求和操作"改成"合併操作"就可以了。

這道題很噁心(對於線段樹初學者來說),很適合鍛鍊程式碼能力,我寫+調了一個早上才過,同機房的大佬們只有兩個調到了晚上才過,大多數人還在調……

Code

#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=1e5+10,inf=0x3f3f3f3f;
ll n,m,x,y,a[N],ans;
struct stru{
	ll val,l,r;//區間與值
	ll qz,qr,hz,hl;//維護前後綴
	ll zz,zl,zr;//維護中綴
	#define ls p<<1
	#define rs p<<1|1
}t[4*N];
inline ll read(){
	int x=0,w=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();
	return w*x;
}stru merge(stru a,stru b){//合併
	stru p;//合併結果區間
	//特判
	if(a.l==0&&a.r==0&&b.l==0&&b.r==0)return stru{0,0,0,0,0,0,0,0,0,0};
	if(a.l==0&&a.r==0)return b;
	if(b.l==0&&b.r==0)return a;
	//左右端點
	p.l=a.l,p.r=b.r;
	//區間和
	p.val=a.val+b.val;
	//字首
	if(a.val+b.qz>a.qz)p.qz=a.val+b.qz,p.qr=b.qr;
	else p.qz=a.qz,p.qr=a.qr;
	//字尾
	if(b.val+a.hz<b.hz) p.hz=b.hz,p.hl=b.hl;
	else p.hz=b.val+a.hz,p.hl=a.hl;
	//中綴
	int qjh=a.hz+b.qz;//前加後
	if(a.zz>=qjh&&a.zz>=b.zz)p.zz=a.zz,p.zl=a.zl,p.zr=a.zr;
	else if(qjh>=b.zz)p.zz=qjh,p.zl=a.hl,p.zr=b.qr;
	else p.zz=b.zz,p.zl=b.zl,p.zr=b.zr;
	return p;
}void build_tree(int p,int l,int r){
	#define f t[p]
	f.l=l,f.r=r;
	if(l==r){
		f.zz=f.val=read();
		f.qz=f.hz=f.val;
		f.qr=f.zl=f.zr=f.hl=l;
	}else{
		int mid=(l+r)>>1;
		build_tree(ls,l,mid),build_tree(rs,mid+1,r);
		f=merge(t[ls],t[rs]);
	}
}stru query(int p,int l,int r){
	int le=t[p].l,ri=t[p].r;
	stru tmp=stru{0,0,0,0,0,0,0,0,0,0};
	if(le>r||ri<l)return tmp;
	if(le>=l&&ri<=r)tmp=t[p];
	else tmp=merge(query(p*2,l,r),query(p*2+1,l,r));
	return tmp;
}
int main(){
	freopen("date.in","r",stdin);
	n=read(),m=read();
	build_tree(1,1,n);
	while(m--){
		x=read(),y=read();stru ans=query(1,x,y);
		printf("%lld %lld %lld\n",ans.zl,ans.zr,ans.zz);
	}
	return 0;
}

本文來自部落格園,作者:Keven-He,轉載請註明原文連結:https://www.cnblogs.com/Keven-He/p/15820082.html