線段樹

————————————18.4.18更新

有時我們遇到一些非常噁心神犇的題:數據量又大,又要做在線修改在線返值的題,顯然這時我們不可能用O(n)的數組一個一個地去修改,去求值。

這時,我們就需要用到線段樹了!!!!!!

讓我們來看看吧

線段樹的簡介

我們設置一個二叉樹,並定義根節點的區間範圍是(1,n)。

我們設一個點的區間是(l,r),則每個點的左兒子的區間範圍是(l,(l+r)/2),右兒子的區間範圍是((l+r)/2+1,r)。同時,它的左兒子的編號是它的編號的2倍,右兒子是編號2倍加一。


這樣,我們就得到了一棵線段樹了。。。請注意,它是一個嚴格的二叉平衡樹

每個節點的都存儲它表示區間的各種信息,如:該區間的最值,所有點的和,乘積。。。。而一個節點的值我們可以根據它的兩個兒子的值求出來。

接下來的操作,如:求和,求最值,求乘積,修改區間等就可以在樹上操作了,具體實現會在後面講到

線段樹的優劣

首先,線段樹樹的修改和查詢都是o(log2n)的。所以說相比之前的暴力這就很coooooool了。

線段樹的空間複雜度是o(n)的,但是實際上空間是n*8的,所以相比某些算法要差勁些。(例如,離線時差分數組(而且差分數組的空間僅爲o(n))求和更厲害,倍增法求最大更厲害)

但是線段樹是在線的,這就是它的優越性。樹狀數組也是在線的,常數比它小,但是線段樹可以求最值,所以是很優秀的!!!!

線段樹的代碼實現

建樹:在開始計算時,我們的線段樹還沒建起,所以需要初始化一下。

struct node {  
    LL sum,l,r,lazy,maxx; //maxx表示該區間的最大值,sum表示該區間的值,l,r表示該區間的左右界,lazy表示懶值 
} a[maxn];  
void btree(LL l,LL r,LL x) {  //l,r表示目前點的左右界,x表示目前節點的編號 
    if(l==r) {  //若該點的左右界相同了,則不往下繼續建樹了 
        a[x].l=l;  
        a[x].r=r;
	a[x].sum=s[l];  //該點的和就是它的初始值 
	a[x].maxx=a[x].sum;//該點的最值就是它的初始值 
        return;  
    }  
    LL p=x<<1;  
    a[x].l=l;  
    a[x].r=r;  
    btree(l,(l+r)>>1,p);   //建立左兒子,其編號爲2*x 
    btree(1+((l+r)>>1),r,p+1);  //建立右兒子,其編號爲2*x+1 
	a[x].sum=a[p].sum+a[p+1].sum;//它的區間值等於左兒子區間值加右兒子區間值 
	a[x].maxx=max(a[p].maxx,a[p+1].maxx);  //它的最值等於左兒子最值與右兒子最值的最值 
}

修改:我們要修改某個區間的值,如將某區間的所有點加某值。

如果暴力修改所有被包含區間後,我們發現在數據很大時會超時,這是爲什麼呢?因爲修改某個區間我們發現要逐個修改該區間的所有樹點,這樣就無法發揮線段樹的作用。這時其實我們先將修改時遇到的點權先更新一下,保證這個點的祖先們的值能得到更新,再給它的左右兒子上累計博主曾經因爲這個改了一天的代碼懶值,當我們詢問到有懶值的點時,我們就下方懶值即可。(注:該懶值思想在後面用得非常多,請一定深刻理解)

void put(LL x){//下放操作 
	a[x].sum+=a[x].lazy*(a[x].r-a[x].l+1);//該點的和就是該點值加上它的區間長度與懶值的乘積 
	a[x].maxx+=a[x].lazy;//該區間的最大值就是它的最大值加上懶值 
	a[x<<1].lazy+=a[x].lazy;//累計左兒子的懶值 
	a[(x<<1)+1].lazy+=a[x].lazy;//累計右兒子的懶值 
	a[x].lazy=0;//該點下放完成 ,懶值爲0 
}  
void modify(LL x,LL l,LL r,LL q) {//q表示區間全部點加上q  
    if(a[x].r<l||r<a[x].l)return;//如果詢問到的區間左右區間超出的,就不再修改。 
	if(a[x].lazy)put(x);  //遇到有懶值就下放(注:此處可不下放,博主爲了保險起見就修改了) 
    if(l<=a[x].l&&a[x].r<=r) {//如詢問區間被修改區間完全被包含 ,則開始修改 
        a[x].lazy+=q;//加上懶值 
		put(x);  //下方更新該點 
        return ;  
    }  
    LL p=x<<1;  
    modify(p,l,r);  //更新左兒子 
    modify(p+1,l,r);  //更新右兒子 
    a[x].sum=a[p].sum+a[p+1].sum;  
    a[x].maxx=max(a[p].maxx,a[p+1].maxx);
}

查詢:查詢某個區間的和(最大值請自行思考博主懶得寫了

LL getsum(LL x,LL l,LL r) {//查詢區間和  
    if(a[x].r<l||r<a[x].l)return  0; //如果該區間超出查詢區間時,則返回0 
    if(a[x].lazy)put(x);// 遇到有懶值就下放
    if(l<=a[x].l&&a[x].r<=r) {  
        return a[x].sum;  // 如詢問區間被修改區間完全被包含 ,則返回該區間的和 
    }  
    LL p=x<<1,sl=getsum(p,l,r),sr=getsum(p+1,l,r);  //求左右所有兒子的和值 
    return sr+sl;  //返回該點的左右兒子區間和的值 
}

總結線段樹:

非常優秀。。。在其他算法中也及其有用,如樹狀剖分等算法。。。。

博主打字很辛苦,希望各位神犇能夠多多光顧該博。。謝謝



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章