1. 程式人生 > 其它 >圖論專題-學習筆記:樹上差分

圖論專題-學習筆記:樹上差分

目錄

1. 前言

樹上差分,是一種難度不高,思維量也不大的演算法,應用範圍比較窄但是快。

前置知識:差分,樹上最近公共祖先(LCA)。

2. 詳解

2.1 點上差分

先來看這麼一個例題:

給出一棵 \(n\) 個點的樹,每個點點權初始為 0,現在有 \(m\) 次修改,每次修改給出 \(x,y\),將 \(x,y\) 簡單路徑上的所有點權 \(+d\),問修改完之後每個點的點權。

\(1 \leq x,y \leq n \leq 10^6,m \leq 10^6\)

如果你會樹鏈剖分,你能夠直接秒了這道題,因為從樹剖的角度看這就是個板子。

然而樹剖解決這種問題的複雜度是 \(O(n \log^2 n)\),雖然常數小並且跑不滿,但是還是跑不過 \(10^6\)

於是我們需要一種更加簡潔的演算法,就是樹上差分。

首先簡要回顧一下序列上的差分:

序列上的差分在處理區間加(設區間為 \([l,r]\),加上的數為 \(d\))的時候,採用的方法是在 \(a_l\) 加上 \(d\),在 \(a_{r+1}\) 減去 \(d\),然後做一遍字首和。

這樣做的好處就是將區間加改成了單點加,簡化了操作。

那麼考慮將序列差分轉移到樹上:

比如我們要對 \(x,y\) 的路徑上的點統一加上 \(d\)

那麼我們仿照序列差分的形式,首先 \(a_y\)

加上 \(d\)\(a_x\) 加上 \(d\)

發現我們影響的點是 \(x,h,b,f,y\),因此對於點 \(a\) 我們不能有影響,操作方案就是 \(a_b\) 減去 \(d\)\(a_a\) 減去 \(d\)

最後我們對整棵樹做一遍 dfs,將所有點的點權變為其子樹(含自己)內所有點的點權,這個操作仿照求每個點子樹的 Size 就可以完成了。

這麼做的正確性是什麼呢?

觀察 \(a_y+d,a_x+d\),發現這兩個操作對於 \(a,b\) 及以上(這裡沒有)的點造成了影響,\(a\) 及以上的點是雙倍影響(\(2 \times d\)),\(b\) 是單倍影響。

因此處理方案就是首先在 \(b\)

這裡減去 \(d\),然後 \(a\) 這裡減去 \(d\),這樣 \(a\) 及以上的點影響就消除了,\(b\) 這個點也是正常的加上 \(d\)

發現 \(b\)\(lca(x,y)\),於是對於一次修改操作就是這樣的:

\(a_x \leftarrow a_x+d,a_y \leftarrow a_y+d,a_{lca(x,y)} \leftarrow a_{lca(x,y)}-d,a_{fa_{lca(x,y)}} \leftarrow a_{fa_{lca(x,y)}}-d\)

這裡需要注意的是程式碼中根節點 \(root\)的父親應該設為一個虛擬節點,否則 \(lca(x,y)=root\) 的時候會出問題。

上述問題就是點上差分,叫做點上差分的原因是因為這類問題是操作點權的。

2.2 邊上差分

還是看這麼一個例題:

給出一棵 \(n\) 個點的樹,每條邊邊權初始為 0,現在有 \(m\) 次修改,每次修改給出 \(x,y\),將 \(x,y\) 簡單路徑上的所有邊權 \(+d\),問修改完之後每條邊的邊權。

\(1 \leq x,y \leq n \leq 10^6,m \leq 10^6\)

會樹剖的同學還是能秒了這道題,當然複雜度還是會炸。

接下來看看樹上差分的優秀做法吧~

首先我們需要一種叫做“邊權轉點權”的方法,就是對於每個點我們認為其點權代表這個點與其父節點之間的邊的邊權,對於每條邊我們認為其邊權是這條邊所連兩個點中深度較大的點的點權,根節點點權無意義。

然後我們就可以開始利用樹上差分了,還是修改 \(x,y\) 路徑上的邊,還是這張圖:

發現此時改的點只有 \(x,y,h,f\) 了,於是我們這麼操作:\(a_x \leftarrow a_x+d,a_y \leftarrow a_y+d,a_{lca(x,y)} \leftarrow a_{lca(x,y)}-2d\)

這麼做的正確性可以仿照點上差分說明,這裡不再贅述。

同樣的做完之後一遍 dfs 求一下每個點的點權即可。

上述問題叫做邊上差分,是因為該類問題在邊上操作邊權。

點上差分和邊上差分其實區別不大,只是處理細節稍微有些不同而已。

2.3 例題

例題:P3258 [JLOI2014]松鼠的新家

這道題是點上差分的例題,就是需要注意一下求完所有點真的點權之後對於所有 \(a_i(i \geq 2)\) 這些點而言,點權需要減一,因為這些點並不會被走兩次。

Code:GitHub CodeBase-of-Plozia P3258 [JLOI2014]松鼠的新家.cpp

3. 總結

樹上差分就是將路徑加轉變為了單點加,仿照差分思路解題而已。

實際上你會發現樹上差分複雜度是 \(O(n+m)\) 的,並且碼量相對較小,於是對於一些 \(n,m\) 比較大的題(樹剖過不去)或者是樹剖碼量大的題,樹上差分能夠起到作用。