1. 程式人生 > 其它 >【學習筆記】線段樹淺談

【學習筆記】線段樹淺談

【學習筆記】線段樹淺談

線段樹,顧名思義,就是一棵樹,上面的每個節點由一個“線段”構成,然後組成了一棵除去了最後一層外,是完全二叉樹的樹,又叫區間樹,作用主要是單點修改,區間修改和區間查詢,這裡拿線段樹的區間和來舉例子

線段樹的構建

線段樹是一棵完全二叉樹,我們可以可以按照與二叉堆相似的儲存節點的方式,根節點為k,左子樹為k<<1,右子樹則為k<<1|1,因為樹是遞迴定義的,所以我們開始遞迴建樹。

1.建立結構體,用於存樹

因為完全二叉樹對應一個長度為a的區間,它需要的節點個數為4a,因此,我們要把樹的大小開到區間長度的四倍

const int maxn=1000010;
struct tree{
	int l,r;
	long long dat,add,mul;
}t[maxn*4+5];

2.建立上傳

因為每次建一棵樹,父節點都要有相對應的資料更新,因此我們需要pushup來上傳子節點的資料

void pushup(int k)
{
	t[k].dat=(t[k<<1].dat+t[k<<1|1].dat)%p;
}

3.遞迴建樹

然後用遞迴建樹

//k代表當前節點,l代表當前節點的左端點,r代表當前節點的右端點 
void build(int k,int l,int r)
{
	//儲存一下左端點和右端點,方便以後呼叫
	//同時做一下延遲標記的初始化 
	t[k].l=l,t[k].r=r;
	t[k].add=0,t[k].mul=1;
	if(l==r)
	{
		t[k].dat=a[l];//如果到區間長度為1的時候,那麼這個時候就等於對應的數字 
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);//遞迴建左子樹 
	build(k<<1|1,mid+1,r);//遞迴建右子樹 
	pushup(k);//向父節點輸送左子樹和右子樹的資料 
	t[k].dat%=p;
	return ;
}

這樣,一棵線段樹就建好了

線段樹的操作

對於線段樹,我們肯定不僅僅滿足於建好一棵線段樹,最主要的是,我們要用它做一些什麼事情,來幫助我們更好地解決我們需要解決的問題。

前面說過,線段樹的操作主要是點的修改和區間的資料查詢,我們先說一說點的修改

1.點的修改

因為線段樹是用遞迴定義的,因此,我們點的修改也可以用遞迴來進行修改這裡不過多贅述,因為重要的在後面

2.區間修改

區間修改有兩種修改方式,第一種是對應的某一個區間裡面的數,每一個加上一個數,另一種是對應區間裡面的數,每一個乘上一個數字。

對於加上一個數的方式,我們直接類似於點的修改,但是我們會發現,有一些樹是並沒有用到的,也就是說,我們每次在更新的時候去更新下一個節點,然後資料繼續往下更新,我們就會造成很大的空間與時間的冗餘,這些空間和時間都很大,我們都用得到!而且可以節省很大一部分空間!

因此,我們在這裡就要使用一種特殊的操作,延遲標記,然後我們就可以節省大量的空間,以及時間,從而加快線段樹的速度

大家可以看到,在之前的結構體定義中有add和mul,它們就分別是線段樹加法和乘法的延遲標記

然後延遲標記在我們使用某棵樹的時候需要傳遞下去,因此,我們與pushup相類似,我們要使用一個pushdown

void pushdown(int k)
{
	//傳遞節點 
	t[k<<1].dat=(t[k<<1].dat*t[k].mul+t[k].add*(t[k<<1].r-t[k<<1].l+1))%p;
	t[k<<1|1].dat=(t[k<<1|1].dat*t[k].mul+t[k].add*(t[k<<1|1].r-t[k<<1|1].l+1))%p;
	//傳遞乘法的懶惰標記 
	t[k<<1].mul=(t[k].mul*t[k<<1].mul)%p;
	t[k<<1|1].mul=(t[k].mul*t[k<<1|1].mul)%p;
	//傳遞加法的懶惰標記 
	t[k<<1].add=(t[k<<1].add*t[k].mul+t[k].add)%p;
	t[k<<1|1].add=(t[k<<1|1].add*t[k].mul+t[k].add)%p;
	//還原父節點,因為子節點已經更新過了 
	t[k].add=0;
	t[k].mul=1;
}

然後我們就可以快樂的進行區間修改了

void updata1(int k,int l,int r,int v)
{
	if(l<=t[k].l&&r>=t[k].r)
	{
		t[k].dat=(t[k].dat*v)%p;
		t[k].mul=(t[k].mul*v)%p;
		return ;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(l<=mid)
	updata1(k<<1,l,r,v);
	if(r>mid)
	updata1(k<<1|1,l,r,v);
	pushup(k);
}
void updata2(int k,int l,int r,int v)
{
	if(l<=t[k].l&&r>=t[k].r)
	{
		t[k].dat=(t[k].dat+v*(t[k].r-t[k].l+1)%p;
		t[k].mul=(t[k].add+v)%p;
		return ;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(l<=mid)
	updata2(k<<1,l,r,v);
	if(r>mid)
	updata2(k<<1|1,l,r,v);
	pushup(k);
}

3.區間查詢

區間查詢和區間修改實際上差不多,但是要注意返回值的時候要

模,不然會出問題

long long query(int k,int l,int r)
{
	if(l<=t[k].r&&r>=t[k].r)
	return t[k].dat;
	pushdown(k);
	long long ans=0;
	int mid=(l+r)>>1;
	if(l<=mid)
	ans=(ans+query(k<<1,l,r))%p;
	if(r>mid)
	ans=(ans+query(k<<1|1,l,r))%p;
	return ans%p; 
}

這就是線段樹的模板了

最終程式碼

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxn=100010;
int n,m,p;
int a[maxn];
struct tree{
	int l,r;
	long long dat,add,mul;
}t[maxn*4+5];
void pushup(int k)
{
	t[k].dat=(t[k<<1].dat+t[k<<1|1].dat)%p;
}
void pushdown(int k)
{
	//傳遞節點 
	t[k<<1].dat=(t[k<<1].dat*t[k].mul+t[k].add*(t[k<<1].r-t[k<<1].l+1))%p;
	t[k<<1|1].dat=(t[k<<1|1].dat*t[k].mul+t[k].add*(t[k<<1|1].r-t[k<<1|1].l+1))%p;
	//傳遞乘法的懶惰標記 
	t[k<<1].mul=(t[k].mul*t[k<<1].mul)%p;
	t[k<<1|1].mul=(t[k].mul*t[k<<1|1].mul)%p;
	//傳遞加法的懶惰標記 
	t[k<<1].add=(t[k<<1].add*t[k].mul+t[k].add)%p;
	t[k<<1|1].add=(t[k<<1|1].add*t[k].mul+t[k].add)%p;
	//還原父節點,因為子節點已經更新過了 
	t[k].add=0;
	t[k].mul=1;
}
//k代表當前節點,l代表當前節點的左端點,r代表當前節點的右端點 
void build(int k,int l,int r)
{
	//儲存一下左端點和右端點,方便以後呼叫
	//同時做一下延遲標記的初始化 
	t[k].l=l,t[k].r=r;
	t[k].add=0,t[k].mul=1;
	if(l==r)
	{
		t[k].dat=a[l];//如果到區間長度為1的時候,那麼這個時候就等於對應的數字 
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);//遞迴建左子樹 
	build(k<<1|1,mid+1,r);//遞迴建右子樹 
	pushup(k);//向父節點輸送左子樹和右子樹的資料 
	t[k].dat%=p;
	return ;
}
void updata1(int k,int l,int r,int v)
{
	if(l<=t[k].l&&r>=t[k].r)
	{
		t[k].dat=(t[k].dat*v)%p;
		t[k].mul=(t[k].mul*v)%p;
		return ;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(l<=mid)
	updata1(k<<1,l,r,v);
	if(r>mid)
	updata1(k<<1|1,l,r,v);
	pushup(k);
}
void updata2(int k,int l,int r,int v)
{
	if(l<=t[k].l&&r>=t[k].r)
	{
		t[k].dat=(t[k].dat+v*(t[k].r-t[k].l+1))%p;
		t[k].mul=(t[k].add+v)%p;
		return ;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(l<=mid)
	updata2(k<<1,l,r,v);
	if(r>mid)
	updata2(k<<1|1,l,r,v);
	pushup(k);
}
long long query(int k,int l,int r)
{
	if(l<=t[k].r&&r>=t[k].r)
	return t[k].dat;
	pushdown(k);
	long long ans=0;
	int mid=(l+r)>>1;
	if(l<=mid)
	ans=(ans+query(k<<1,l,r))%p;
	if(r>mid)
	ans=(ans+query(k<<1|1,l,r))%p;
	return ans%p; 
}
int main()
{
	
}
本博文為wweiyi原創,若想轉載請聯絡作者,qq:2844938982