Re 從零開始的演算法總結》(3) 差分與線段樹

2022-06-28 07:00:10 字數 3657 閱讀 8405

如果有一數列 a[1],a[2],.…a[n]

且令 b[i]=a[i]-a[i-1](i>1),b[1]=a[1]

那麼就有

a[i]=b[1]+b[2]+.…+b[i]

=a[1]+a[2]-a[1]+a[3]-a[2]+.…+a[i]-a[i-1]

此時b陣列稱作a陣列的差分陣列

換句話來說a陣列就是b陣列的字首和陣列 例:

原始陣列a:9 3 6 2 6 8

差分陣列b:9 -6 3 -4 4 2

可以看到a陣列是b的字首和

字首和是乙個陣列的某項下標之前(包括此項元素)的所有陣列元素的和。

且有在區間[left,right]上加乙個常數c,可以利用原陣列就是差分陣列的字首和這個特性,可以讓b[left]+=c,b[right]-=c

當使用字首和求某一區間的和,如果操作次數過大,那麼字首和會超市,可以採用樹狀陣列或線段樹

題目:

來先看一道裸題,有n個數。

m個操作,每一次操作,將x~y區間的所有數增加z;

最後有q個詢問,每一次詢問求出x~y的區間和。

思路:

很明顯,直接用字首和無法快速滿足這個操作,所以我們就用到了查分陣列。

設a陣列表示原始的陣列;

設d[i]=a[i]-a[i-1](1設f[i]=f[i-1]+d[i](1設sum[i]=sum[i-1]+f[i](1即先求差分,再修改區間,隨後再重新生成字首和,最後求和

#includeusing namespace std;

int main()

//修改差分陣列

for (int i = 0; i < m; i++)

//重新生成字首和陣列

fnl[0] = sub[0];

for (int i = 1; i < n;i++)

//生成前n位和

sum[0] = fnl[0];

for (int i = 1; i < n;i++)

//輸出區間x-y的和

for (int i = 0; i < q;i++)

return 0;

}

語文考試結束了,成績還是一如既往地有問題。

語文老師總是寫錯成績,所以當她修改成績的時候,總是累得不行。她總是要一遍遍地給某些同學增加分數,又要注意最低分是多少。你能幫幫她嗎?

第一行有兩個整數n,p,代表學生數與增加分數的次數。

第二行有n個數,a1~an,代表各個學生的初始成績。

接下來p行,每行有三個數,x,y,z,代表給第x個到第y個學生每人增加z分。

輸出僅一行,代表更改分數後,全班的最低分。

#include#includeusing namespace std;

int src[5000001];

int con[5000001];

int main()

for (int i = n - 1; i > 0; i--)

for (int i = 0; i < p; i++)

min = con[0] = src[0];

for (int i = 1; i < n; i++)

printf("%d", min);

return 0;

}

線段樹是由一條一條線段組成的一棵樹,每個結點都是一條線段,每個非葉子結點的子結點都是對此線段的再劃分,假設此線段範圍為[left,right],其左孩子的線段為[left,mid],右孩子的線段為[mid,right],可以使用乙個陣列儲存所有結點,若父節點為k,左孩子為2k,右孩子為2k+1,葉子結點是單元結點(即孤立的點)。

每個結點包括的資訊有:

線段樹還是一棵二叉搜尋樹,主要用於高效解決連續區間的動態查詢問題。

struct node
思路:

對於每乙個節點,給左右端點確定範圍

如果是葉子節點,儲存相關資訊

父節點狀態合併

void build(int l,int r,int k)

int m = (l+r)/2;

build(l,m,k*2);//左孩子

build(m+1,r,k*2+1);//右孩子

tree[k].w = tree[k*2].w+tree[k*2+1].w;

}

思路:

如果當前列舉的點左右端點相等,即葉子節點,就是目標節點。

如果不是,因為這是二分法,所以設查詢位置為x,當前結點區間範圍為了l,r,中點為mid,則如果x<=mid,則遞迴它的左孩子,否則遞迴它的右孩子。

void ask(int k)

if(tree[k].f) down(k);//在區間修改中講解

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) ask(k*2);//目標位置比中點靠左,就遞迴左孩子

else ask(k*2+1);//反之,遞迴右孩子

}

思想:

首先利用單點查詢的思想找到目標結點x。

利用建樹時合併的思想將目標結點以及其父節點進行修改。

void add(int k)

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) add(k*2);

else add(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;//所有包含結點k的結點狀態更新

}

思想:

當前區間和待查詢區間一共有三種情況:

當前結點區間的值全部是帶查詢區間的一部分,則直接加上當前區間的區間和。

當前結點區間包含了帶查詢的區間,則繼續遞迴左右子區間,直至情況1或情況3。

當前結點區間與帶查詢區間交叉,繼續遞迴直至情況1。

void sum(int k)

if(tree[k].f) down(k);

int m=(tree[k].l+tree[k].r)/2;

if(x<=m) sum(k*2);

if(y>m) sum(k*2+1);

}

如果資料量很大,要求將某個很大的區間的每個元素進行修改,那麼其時間複雜度將會非常大,那麼線段樹演算法的意義就不在了,而且修改某個區間的元素之後不一定會用到該區間的孩子結點的資訊,因此為了降低複雜度,這裡引入懶標記--線段樹的精髓。

void down(int k)

void add(int k)

if(tree[k].f) down(k);//懶標記下傳。只有不滿足上面的if條件才執行,所以一定會用到當前節點的子節點

int m=(tree[k].l+tree[k].r)/2;

if(a<=m) add(k*2);

if(b>m) add(k*2+1);

tree[k].w=tree[k*2].w+tree[k*2+1].w;//更改區間狀態

}

Re 從零開始的動態規劃

有的人,天天在說自己打cf太菜,要學線性規劃,卻連第一步都沒邁出去,不會吧,不會吧,不會那個人就是你吧 f 跑來寫部落格的時間都可以用來學幾個點 咳咳咳言歸正傳,這是我第一篇部落格,用來記錄我假期演算法的學習之路,先立下flag,以後我要每天一更!也許這樣自己就不會咕咕咕 步驟 階段 狀態 決策 策...

Re從零開始的每一天

廣告 日記pro 密碼請私信 雖然 re從零開始的異世界生活 這番我還沒看過 但這番真的太有名了,所以說就想到了,用一下 新的一年,希望我能更好,能把想做的事情堅持下去,也能做到我想做的事情 本日記僅限寒暑假,平時幾乎沒時間,如果記起來的話,會考慮寫一下 時間 time 計畫 plan 詳細資訊 d...

RE 從零開始的faiss庫的編譯安裝

最近facebook更新了faiss的install.md,所以其實沒什麼好寫的,但是這一路上踩了不少坑還是稍作記錄。雙系統安裝教程 win10 ubuntu16.04雙系統 gcc g gfortran安裝和降級 推薦安裝gcc6以下版本,方便後面安裝gpu和faiss。ubuntu16.04預設...