差分
(姍姍來遲的一篇學習筆記)
Part 1 一維差分
P2367 語文成績
給出一個序列,支援修改,查詢最小值。
在不考慮資料結構的情況下,有一種東西,叫差分。
他可以做到 \(O(1)\) 修改,\(O(n)\) 查詢。
首先假設有一個序列:\(1,6,8,5,10\) 。
他的差分陣列就是:\(1,5,2,-3,5\),\(d_i=a_i-a_{i-1}\)。
如果是要對區間 \([l,r]\) 加上某個數 \(x\) 的話,只要在把 \(d[l]\) 加上 \(x\),\(d[r+1]\) 減去 \(x\) 即可。
原因就是,在修改完以後,我們需要遍歷一次序列去更新新的 \(a_i\)。 \(a_i=a_{i-1}+d[i]\),假設 \(x\) 修改了 \([l,r]\),\(a_{l+1}\) 就必然加上 \(x\),由於 \(d_{l+2}\) 並沒有改變,也就是相差的數不變。加上這個數,\(a_{l+2}\) 一樣會更新。但是到了 \(a_{r+1}\) 我們就不必加了,所以要減去 \(x\)。
\(Code\)
P1083 借教室
題目裡邊藏了很多細節,稍不注意到就會錯過正解。
-
一旦遇到第一個不滿足的,就直接停止分配。
-
按照先後順序來借。先到先得。
看到第二個性質,不難想到二分查詢來判斷是否滿足條件。
而對於從 \([l,r]\) 中借教室,可以使用差分輕鬆實現。差分陣列儲存的是每天的借的教室個數。
而二分的判斷閉區間,就是對於 \([1,l]\) 是否可以滿足借的原則來進行的一個二分查詢 \(l\)。
判斷內容就是差分,查詢的時候看每一天是否滿足 \(room[i]\) 要求即可。
\(Code\)
Part 2 二維差分
和二維字首和大同小異。
回顧一下二維字首和:
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]
P3397 地毯
先考慮修改,時間是 \(O(1)\) 的。對四個方向操作即可。
再考慮查詢:
透過看查詢的步驟,就知道操作的意義了。其實和一維差分的性質是一樣的。邊界開頭就增加,到了邊界就減去。
在遍歷的時候就是 \(O(n^2)\),即:
d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+a[i][j]
於是就結束了。瞭解本質即可。
\(Code\)
Part 3 樹上差分
前置知識:LCA,可以左轉 LCA學習筆記
回顧差分的最基本本質:在頭(首位)加上修改的數,尾部減去修改的數,在遍歷中就可以實現對這個序列的操作。
樹上差分有兩種,一種是點差分,另外一種是邊差分。
先考慮邊差分。
我們設 \(d[i]\) 表示從 \(i\) 到他的父親的邊的修改的值的和。
假設我們需要給 \(p\) 到 \(q\) 之間的邊加上一個 \(x\)。
由於樹有兩個性質:
- 任意兩個結點之間有且只有一條路徑。
- 一個結點只有一個父結點(即只有一條返祖邊)。
所以對兩點之間的邊的修改,就可以拆成\([p,lca(p,q)]\),\([q,lca(p,q)]\) 這兩條路徑。
於是乎就是數列上的差分了。就是
$ d[p]+=x,d[lca(p,q)] -=x,d[q]+=x,d[lca(p,q)] -=x $。
我們只需要如下操作即可:
注意,在計算的時候,是由下往上回溯的,統計的是以 \(u\) 為根的子樹的和,如果當前的點 \(u\) 高度大於 \(p\) 且包含 \([lca(p,q),p]\) 內的結點,那麼他的子樹內必定包含 \([q,lca(p,q)]\) 的點,所以 \(d[lca(q,p)]\) 只會減一次且不會減多。
第二種就是點差分。我們就用 \(d[i]\) 表示當前 \(i\) 的點所增加的值。我們還是增加 \([p,q]\) 間點的路徑,我們首先是對 \(d[p]+=x,d[q]+=x\),但是這樣的話 \(lca(p,q)\) 就會多加一個 \(x\) ,我們需要減去。但是現在還有一個 \(x\) 沒有減去,按照一維差分,所以我們要在 \(lca(p,q)\) 的父親處減去一個 \(x\)。
所以點差分的操作就是:
P8805 機房
樹上字首和模板,拿來練手的,不多說了。
自己寫的題解
P3128 [USACO15DEC]Max Flow P
模板題,顯而易見的事情,每次對隔間,也就是點 \(p,q\) 進行操作,板子點差分,不講。
\(Code\)
P3258 松鼠的新家
每次對參觀的兩個點進行操作,但是要注意,兩次操作之間的起始點是不算的。在最後需要減去。注意終點也要減一。
剩下的還是點差分。
\(Code\)