良心之作,史上最全字首和與差分!!!

2021-09-25 10:17:18 字數 3453 閱讀 5866

1.字首和:對乙個序列進行o(n)預處理(或者乙個矩陣,即二維字首和)後,o(1)地查詢任意一段子串行的和.

2.差分:用於求解多次區間修改與區間詢問的題型,例如多次給[ l , r ] 內所有數 + val,就可以用差分以及字首和來優化。區間操作o(1),區間詢問o(n)處理,o(1)查詢.

3.樹上差分:同樣的,如果我們有若干次操作與若干次詢問,每次操作對從u 到 v 路徑上所有節點加乙個 值,那麼我們用樹上差分可以將時間複雜度控制在o(1)上,詢問同樣是o(n)處理,o(1)查詢.

注意:差分為離線演算法,不支援同時修改同時詢問操作,演算法複雜度會退化1.1 問題描述:給出一串長度為 n 的數列a1,a2,a3…an,再給出m個詢問,每次詢問給出l,r兩個數,要求給出區間[l,r]裡的數的和。

1.2基本思路:樸素演算法,將[l,r]中每個數依此累加,複雜度為o(mn);

字首和的思路:開乙個陣列d;

d[i] = a[i] + a[i-1] + a[i-2] + ... + a[1];
記錄序列從開始至第i位置的總和,由此我們可以得出任意一段序列[1,x]的和,即[1,l-1],[1,r],ans = d[r] - d[l - 1],複雜度為o(1);預處理操作複雜度為o(n);3.1 問題描述:給出一串長度為 n 的數列a1,a2,a3…an , 給出k次操作,每次操作給出l、r、val,要求對 [l , r]內所有元素加上val,再給出m個詢問,每次詢問給出l,r兩個數,要求給出區間[l,r]裡的數的和。

3.2 樸素思路:很容易想到的做法就是每次給出l,r 和 val後,我們就挨個對[l,r] 內所有元素+val,再在 詢問時將答案挨個加起來。這樣時間複雜度為o(kmn)

3.3 差分思路:順應著字首和,假如我們將所有修改操作進行一次字首和,在所有修改操作完成後,進行一次總的修改,即大大降低時間複雜度;

3.4 一維差分實現思路:開乙個c陣列,用來記錄修改的字首和,假設我們對區間[l,r],進行修改,我們只需令c[l] += val,c[r+1] -= val,將所有修改操作類此標記,在最後對c陣列進行一次字首和,將原先處理好的d字首和陣列,按位進行加法,即得最終修改值;

c[l] += val;c[r+1] -= val;

一維差分字首和

c[l] = val;c[l+1] = val;......c[r] = val;c[r+1] = 0;//為什麼?因為我們已經將c[r+1]標記為了 -val;

修改陣列d;

for(i,1...n)

d[i] += c[i];

2.1 公式:
d[i][j] = σ(a[i...0][j...0]);
2.2 基本問題:給定乙個n*m大小的矩陣a,有q次詢問,每次詢問給定x1,y1,x2,y2四個數,求以(x1,y1)為左上角座標和(x2,y2)為右下角座標的子矩陣的所有元素和。注意仍然包含左上角和右下角的元素。

二維字首和實現思路:d[i][j]記錄以i,j為右下角下標,以1,1為左上角下標的矩陣內所有資料域之和;實現詢問操作:

紅色矩陣a,黃色為目標矩陣;ans = (黃,灰,藍,紫四個矩陣) - (灰,藍兩個矩陣) - (藍,紫兩個矩陣)+(藍色矩陣);這裡我們以藍色矩陣為過渡,o(1)時間內求解完畢;預處理操作

//d[i][j]應預先存好第(i,j)位置的元素值;

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

詢問操作
ans = d[x2][y2] - d[x1-1][y2] - d[x2][y1-1] + d[x1-1][y1-1];//x1 - 1和y1 - 1是因為題目要包含左上角和右下角的元素;
思路引進:既然一維字首和能差分,二維同樣可以;(假的證明)

實質上是只不過是把差分從一維引進到二維而已;

同樣開乙個c陣列來維護修改操作;

for(int i  = 1;i <= m;i++)
後續操作均與一維差分類似,在此不再贅述;5.1 問題描述:

· 給定一棵有n個點的樹,所有節點的權值初始時都為0。

· 有k次操作,每次指定兩個點u , v,將 u 到 v 路徑上所有點的權值都+1。

· 請輸出k次操作完畢後權值最大的那個點的權值。

5.2 樸素思路:不用多想,最暴力的做法就是我們找到 u 到 v 路徑上的所有點並+1(可以利用 lca )。最後,再遍歷所有點查詢權值最大的點並輸出。這樣時間複雜度為o(kn),這還不包括 lca 查詢路徑的時間。非常暴力

5.3 求u到v的路徑:那麼我們知道,如果假設我們要考慮的是從 u 到 v 的路徑,u 與 v 的 lca 是 a ,那麼 很明顯,假如路徑中有一點 u′ 已經被訪問了,且 u′ ≠ a ,那麼 u』 的父親也一定會被訪問。(為什麼呢?因為這是一棵樹)所以,我們可 以將路徑拆分成兩條鏈,u -> a 和 a -> v。

(圖糙,輕噴)

a是最近公共祖先,從 u -> v,必然要經過u的父親u』,但並非要經過a的父親,由此類推(由上可知證畢)

5.4樹上差分:常見的樹上差分有兩種形式,即

· 關於邊的差分

· 關於點的差分。

5.4.1 關於邊的差分:

將邊拆成兩條鏈之後,我們便可以像差分一樣來找到路徑了。用 cf[ i ] 代表從i到i的父親這一條路 徑經過的次數。因為關於邊的差分,a 是不在其中的,所以考慮鏈 u -> a,則就要使cf[ u ]++,cf[ a ]− −。然後鏈a -> v,也是cf[ v ]++,cf[ a ]−−。所以合起來便是cf[ u ]++,cf[ v ]++,cf[ a ]−=2。然後, 從根節點,對於每乙個節點x,都有如下的步驟:

(1)列舉x的所有子節點u

(2)dfs所有子節點u

(3)cf[ x ] + = cf[ u ]

那麼,為什麼能夠保證這樣所有的邊都能夠遍歷到呢?因為我們剛剛已經說了,如果路徑中有一點u′已 經被訪問了,且u′≠a,那麼u′的父親也一定會被訪問。所以u′被訪問幾次,它的父親也就因為u′被訪問了幾 次。所以就能夠找出所有被訪問的邊與訪問的次數了。路徑求交等一系列問題就是通過這個來解決的。因 為每個點都只會遍歷一次,所以其時間複雜度為o(n).

5.4.2 關於點的差分:

還是與和邊的差分一樣,對於所要求的路徑,拆分成兩條鏈。步驟也和上面一樣,但是也有一些不 同,因為關於點,u與v的lca是需要包括進去的,所以要把lca包括在某一條鏈中,用cf[ i ] 表示 i 被訪問的 次數。最後對 cf 陣列的操作便是cf[ u ]++,cf[v]++,cf[ a ]−−,cf[ father[a] ]−−。其時間複雜度也是一 樣的o(n).

5.5 樹上差分總結:將需要修改的一段路徑,拆分成兩條鏈,可以理解作兩段序列,然後分別進行一維的差分;

字首和與差分

數列的字首和 sum i 表示a 1 a i 的和 用處1 求i j的和sum j sum i 1 用處2 區間修改。設定乙個change陣列。當區間 i,j 上要加k時,我們令change i k,令change j 1 k。如果我們對change陣列求字首和的話,字首和sum change i ...

字首和與差分

從陣列第乙個開始累加 s i s i 1 a i 求區間 l,r 的和,o 1 複雜度sum s r s l 1 遞推s i j s i j s i 1 j s i j 1 s i 1 j 1 例題 雷射炸彈一種新型的雷射炸彈,可以摧毀乙個邊長為r的正方形內的所有的目標。現在地圖上有n n 1000...

字首和與差分

例題入口 include const int n 320 int a n n a i 1 a i 0 1.對a 求出平方數 將其值置為1 不是平方數就是0 2.對a求乙個字首和 3.對 a,b 求乙個部分和 int sum n n void init for int i 1 i 100000 i i...