關於差分,樹上差分的淺談


  因為網上的關於差分的資料比較少,所以我根據我自己的理解編寫這篇博文。如果你有什么問題,可以聯系我。

--------------------------------------------------------------------------------------------------------

一、差分

  有這樣一道題目:給你一個$m×n$的矩陣,然后使用$k$塊地毯鋪地。每片地毯都給出左下角和右上角坐標。問所有地毯鋪完之后,還有多少個整點(所謂整點,即橫、縱坐標均為整數的點)沒有被地毯覆蓋。

  當然,我們很容易寫出如下的暴力程序(偽代碼):

solve(){
     暴力枚舉每張地毯
     將所有被覆蓋的點均做上標記
     最后再枚舉所有整點,若未被標記則ans+1    
}

  但是,很明顯,這個算法並不能拿到滿分,因為它的空間復雜度為$\Theta(nm)$,但是時間復雜度卻可能達到$\Theta(mnk)$,對一般的比賽來說肯定會無法通過。

  當然,有些大佬可能會說:開$m$棵線段樹可以解決此問題。肯定是可以的,但是對於$NOIp$這種比賽來說,考試時間比較緊促,我是不太贊同這種算法的,因為這樣子編程復雜度太高,甚至可能出現無法調試成功而影響了其它的題目或是影響自己的心情。

  那么,我們應該如何優化這個算法呢?我們考慮一下,主要的時間就是用在枚舉地毯和枚舉被地毯覆蓋的整點上,我們可以對這里進行優化。因為對於每塊地毯,每一行,覆蓋的肯定是一個連續區間。所以我們可以考慮一下前綴和。通過前綴和的方式考慮每個點被地毯覆蓋的次數。如下面表格所示,假如地毯覆蓋了$[2,6]$一段($1$表示地毯在該行的左端點,其中表格第一行為數組下標,第二行為數組值):

1 2 3 4 5 6 7 8
0 1 0 0 0 0 0 0

  通過對數組求前綴和,我們便能得到以下的表格:

 

1 2 3 4 5 6 7 8
0 1 1 1 1 1 1 1

  我們便會發現,通過這種前綴和的形式,能夠在$\Theta(1)$的時間里,實行對一行從某個左端點開始一段區間的修改。但是,我們這個題目中地毯除了有左邊界,還有右邊界啊?不要緊,我們在右邊界后面再減去$1$,就可以保證沒有被覆蓋到的地方不會受到影響。而由於右端點也包括在被覆蓋的范圍內,所以我們要讓$r+1$減去$1$.用上面的表格,如果地毯在該行覆蓋了$[2,6]$一段,我們就將原數組修改成以下所示:

1 2 3 4 5 6 7 8
0 1 0 0 0 0 -1 0

  求前綴和之后,數組就變成如下:

1 2 3 4 5 6 7 8
0 1 1 1 1 1 0 0

  這樣子我們就會發現,所有被地毯覆蓋的點都會變成$1$,而對於每一行,這種操作都是$\Theta(1)$的,所以k塊地毯全部考慮完畢的時間復雜度為$\Theta(kn)$,最后每行做前綴和的時間復雜度為$\Theta(nm)$,這樣子便對以上暴力算法進行了有效的優化。所以我們可以寫出以下的代碼(偽代碼):

solve(){
    從1號地毯考慮到第k塊地毯
    對於每一塊地毯,從右上角坐標行數循環到左下角行數
    將每一行進行修改
    對差分數組求前綴和並累計未被覆蓋地毯的點    
}

二、樹上差分(樹的前綴和)

     近年的$NOIp$,似乎對於樹上差分的題目考察越來越熱(參見$2015$年提高組 運輸計划,$2016$年提高組 天天愛跑步)。這些題目都要知道在樹上從某個點到另一個點的所有路徑。但是,暴力求解這種題目經常會$TLE$。這種題目需要使用樹上差分。在講樹上差分之前,首先需要知道樹的以下兩個性質:

  (1)任意兩個節點之間有且只有一條路徑。

  (2)根節點確定時,一個節點只有一個父親節點

  這兩個性質都很容易證明。那么我們知道,如果假設我們要考慮的是從$u$到$v$的路徑,$u$與$v$的$lca$是$a$,那么很明顯,如果路徑中有一點$u'$已經被訪問了,且$u'$≠$a$,那么$u$'的父親也一定會被訪問,這是根據以上性質可以推出的。所以,我們可以將路徑拆分成兩條鏈,$u$->$a$和$a$->$v$。那么樹上差分有兩種常見形式:(1)關於邊的差分;(2)關於節點的差分。

  ①關於邊的差分:

  將邊拆成兩條鏈之后,我們便可以像差分一樣來找到路徑了。用$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'$被訪問了幾次。所以就能夠找出所有被訪問的邊與訪問的次數了。路徑求交等一系列問題就是通過這個來解決的。因為每個點都只會遍歷一次,所以其時間復雜度為$\Theta(n)$.

  ②關於點的差分:

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

--------------------------------------------------------------------------------------------------------

  通過以上的描述,如果你還是不太能理解,那么以下兩個題目可能可以幫助你理解:

  USACO 最大流(樹上差分)https://www.luogu.org/problem/show?pid=3128

  NOIp2015 運輸計划(樹上差分+二分)https://www.luogu.org/problem/show?pid=2680


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM