————————————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; //返回該點的左右兒子區間和的值
}
總結線段樹:
博主打字很辛苦,希望各位神犇能夠多多光顧該博。。謝謝