1. 程式人生 > 實用技巧 >線段樹入門

線段樹入門

把我csdn的blog搬過來的((將就看

線段樹

想學主席樹,然後開了一篇題解,看到“學習可持久化線段樹之前一定要學懂線段樹”這句話我只好把線段樹又學了一遍((
這塊從昨天到今天學了四個小時,做了三頁紙筆記,所以有錯的可能性不大(但是wtcl,所以還是有很大可能的

概括

線段樹的理解,按照書上的((

  1. 線段樹的每個節點都代表一個區間;
  2. 線段樹具有唯一的根節點,代表的區間是整個統計範圍;
  3. 線段樹的每個葉節點都代表一個長度為1的元區間[x,x];
  4. 對於每個內部節點[l,r],它的左子節點是[l,mid],右子節點是[mid+1,r],其中mid=(l+r)/2;

圖我就不放了

另一個點就是”父子2倍“,即節點i的權值=左兒子權值+右兒子的權值,舉個栗子,節點i的權值編號p,左兒子權值即p*2,右兒子權值即p * 2+1(應該沒說錯吧;

建樹

先講一下線段樹的建樹。
struct陣列儲存我就先略過了;

上程式碼:

void build (int p,int l,int r) {
	t[p].l=l,t[p].r=r;    //節點p代表區間[l,r];
	if (l==r){
		t[p].dat=a[l];
		return;			  //葉節點; 
	} 
	int mid=(l+r)/2;	  //折半(根據“父子二倍”,求mid;
	build(p*2,l,mid);	  //構造左子節點,即[l,mid],編號p*2;
	build(p*2+1,mid+1,r); //同上一步,構造右子節點,即[mid+1,r],編號p*2+1;
t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //從下往上傳遞資訊; } build (1,1,n); //呼叫入口;

  

單點修改

所謂單點修改,就是:

  1. 從根節點出發,遞迴找到代表區間[x,x]的葉節點;
  2. 更新(從下往上)[x,x]以及其所有祖先節點上的資訊;
  3. 時間複雜度為O(logN);

上程式碼:

void change (int p,int x,int v) {
	if (t[p].l==t[p].r) {
		t[p].dat=v;
		return;			//找葉節點; 
	}
	int mid=(t[p].l+t[p].r)/2;	//折半; 
	
	if (x<=mid)			//找x所屬的區間;
change(p*2,x,v); else change(p*2+1,x,v); t[p].dat=max(t[p*2].dat,t[p*2+1],dat); } change(1,x,v); //呼叫入口;

  

區間查詢

遞迴:

  1. [l,r]完全覆蓋了當前節點代表的區間,回溯,該節點的dat值為候選答案;
  2. 左子節點與[l,r]有重疊部分,遞迴訪問左子節點;
  3. 右子節點與[l,r]有重疊部分,遞迴訪問右子節點;

程式碼:

int ask(int p,int l,int r) {
	if (l<=t[p].l &&r>=t[p].r)	return t[p].dat; //完全包含; 
	int mid=(t[p].l+t[p].r)/2; 
	int val=a(1<<30);
	if (l<=mid) val=max(val,ask(p*2,l,r)); //與左子節點有重疊;
	if (r>mid)	val=max(val,ask(p*2+1,l,r)); //與右子節點有重疊;
	return val; 
}
cout<<ask(1,l,r)<<endl; //呼叫入口; 

  

還有兩個點,即區間修改&延遲標記;
區間修改就是結合單點修改和區間查詢…
延遲標記是“在回溯前向節點p加一個標記,標識“該節點曾被修改,但子節點尚未更新””。
這邊就不敲程式碼了((我太菜了怕出錯

總結

有點虎頭蛇尾,畢竟是自己對線段樹的總結可能會有誤;

本篇blog應該適用於線段樹入門QAQ

謝謝大家耐心看完awa