C++算法 線段樹

線段樹這個算法,看起來很是高端,並且頗有用處,因此仍是講一下下吧。算法

舒適提示:寫線段樹前請作好寫碼5分鐘,調試一生的準備^-^數組

啊直接步入正題……優化

首先咱們考慮一個題目:有一個序列,要作到單點修改單點查詢,該怎麼作呢?調試

同窗們先不要着急關掉……咱們細細分析,像這種題,明顯你們都知道……直接暴力就過了嘛……,因此不作分析……blog

而後咱們考慮第二個題目:有一個序列,要作到單點修改區間求和,該怎麼作呢?遞歸

像傳統的for(int i=1;i<=n;i++)ans+=a[i]固然是很香的,但若是遇到一些很是奇怪的題目(數 據 大 到 你 自 閉),這個就沒什麼用了……get

 

啊這,簡單的暴力不能用,就要用複雜的暴力——線段樹,固然是簡單億點點的~由於單點修改能夠O(1),不存在浪費時間的事情,因此咱們用線段樹小小的處理一下就行了。zsh

接下來畫一個圖幫助斯烤:模板

 

 

在這個圖上呢……一共有8個點,就是底下那些最小的線段,而咱們把他合成了許多部分,每一個部分的值就是f[wz*2]+f[wz*2+1](f是每一個的值,wz是每一個的編號)。class

而後就能夠把他一直向下,一直向下,直到發現這個區間是被咱們要求的區間所包含的,而後這個區間內的數就所有加上,就這麼一直向下找找找……,最後就能夠算出一個區間的總和。

下面是演示代碼~

 

void qh(long long wz)//wz是如今所處的小塊編號 
{
	if(tree[wz].l>=a&&tree[wz].r<=b)//a和b是咱們要求的區間的左右端點 
	{
		zshu+=tree[wz].shu;//所有被包含,直接加上 
		return;
	} 
	long long mid=(tree[wz].l+tree[wz].r)/2;//不能被包含?分裂一下試試吧
	if(b>mid)//和諧小細節~ 
	{
		qh(wz*2+1);//若是有被包含,就去右邊的小塊看看 
	}
	if(a<=mid)//和諧小細節*2~(至於爲啥有和諧小細節,以及爲何要這樣寫,請同窗們本身斯烤(這麼簡單仍是能夠的8)) 
	{
		qh(wz*2);//若是有被包含,也要去左邊的小塊看看
	}
}

 

嘿嘿簡單吧^-^,啊我忘記講建樹了QAQ。沒事立刻就講。

建樹嘛,直接遞歸就行了,每次把這個區間分紅兩份,一直分到l和r同樣(就是說這是一個點的值,要輸入了),而後獲取兩個點的值以後就能夠向上遞歸~嗯嗯對,一直遞歸就建好了,相信你們都有這個寫遞歸的能力,但要實在不會就看看下面的代碼吧(舒適提示:線段樹用來建樹的數組要開到節點數的4倍大,至於爲何我也忘了……):

struct hehe
{
	long long l,r,shu,f;
}tree[400005];//tree結構體~ 
long long mid;
void js(long long ll,long long rr,long long wz)//js,建樹的意思。ll和rr分別是左右邊界,wz就是他的編號 
{
	tree[wz].l=ll;//左邊界是ll 
	tree[wz].r=rr;//右邊界是rr 
	if(ll==rr)//左右邊界一致,這是一個點 
	{
		scanf("%lld",&tree[wz].shu);
		return;
	}
	long long mid=(ll+rr)/2;//若是這不是一個點,確定能分紅兩部分 
	js(ll,mid,wz*2);//左邊 
	js(mid+1,rr,wz*2+1);//右邊 
	tree[wz].shu=tree[wz*2].shu+tree[wz*2+1].shu;//加起來 
}

看,是否是很是簡單,接下來爲了學的更深一點點,要把題目加難了(是的還要加難,但我相信大家必定能夠學會的)

有一個序列,要作到區間增長區間求和,該怎麼作呢?

啊這,這個是線段樹裏最難的一部分(起碼我以爲最難),直接下放顯然不太現實……,會浪費掉不少沒必要要的時間。正所謂科技發展在於懶人~,其實咱們也能夠在不影響結果的狀況下偷個懶是吧QWQ。

就像加同樣~我知道這兩個序列的和,我就不必去求全部單個序列是多少,加法和這個差很少,咱們能夠看看有那個序列是全都要加的,而後直接算出它加完以後的值。這樣求這個值的時候確定是不影響計算的(偷懶成功!)。但這時就會有一些同窗吐槽:"啊你這個不嚴謹啊,若是下一次求和是求這個序列的一部分怎麼辦呢?"其實呢,我剛纔也寫了是吧……

其實咱們也能夠在不影響結果的狀況下偷個懶是吧

我剛纔說的是不影響,但這個操做明顯影響了,在哪裏影響了呢?就是隻加了一個總序列,沒有讓他的一部分加上(偷懶失敗QWQ)。但好像根本沒有必要調整他的一部分啊,由於目前根本用不到,沒說讓作的事情咱們還要去作不是浪費時間嗎?但又不能不加,怎麼辦呢?咱們能夠設定一個懶標記,表示它以前被加了多少,這樣呢,每次要求和的時候只要下放這個懶標記,而且加上該加的數,就能達到不說不作,最大程度優化時間,還保證正確hhhhh(偷懶成功)

至於代碼的實現也是很是的簡單:

void xf(long long wz)
{
	tree[wz*2].shu+=(tree[wz*2].r-tree[wz*2].l+1)*tree[wz].f;//每一個長度單位都加上tree[wz].f,總值就增長了(tree[wz*2].r-tree[wz*2].l+1)*tree[wz].f
	tree[wz*2].f+=tree[wz].f;//以前每個長度單位要加tree[wz*2].f,如今發現它還要加上tree[wz].f,就把他們兩個加起來就行了嘛,很容易的。 
	tree[wz*2+1].shu+=(tree[wz*2+1].r-tree[wz*2+1].l+1)*tree[wz].f;
	tree[wz*2+1].f+=tree[wz].f;//同理
	tree[wz].f=0;//這裏已經加過了,要清零的,就像轉帳同樣,你給另外一我的轉了錢,你錢就沒了。 
}

上方是下放懶標記的代碼,下方是區間修改代碼:

void xg(long long wz)
{
	if(tree[wz].l>=a&&tree[wz].r<=b)
	{
		tree[wz].shu+=(tree[wz].r-tree[wz].l+1)*c;//更改值 
		tree[wz].f+=c;//增長懶標記 
		return;
	}//啊後面的都說過,不打了… 
	tree[wz].shu+=(min(tree[wz].r,b)-max(tree[wz].l,a)+1)*c;
	long long mid=(tree[wz].l+tree[wz].r)/2;
	if(b>mid)
	{
		xg(wz*2+1);
	}
	if(a<=mid)
	{
		xg(wz*2);
	}
}

至於加了懶標記之後別的代碼也是要動的。

好比在原來的求和代碼裏多了一個xf(wz);

至於加在哪裏同窗們本身斯烤吧(我太仁慈了)

啊我再放一道線段樹模板題,你們能夠秒掉來吊打我:模板題傳送們

好了我以爲我講完了,若是有什麼很差或漏掉的地方你們能夠及時評論,我會更改的。

相關文章
相關標籤/搜索