進階樹論

 

建國中學 賴昭勳

Euler Tour & DFS

樹壓平

樹的DFS順序 - 1

1

2

3

4

5

6

7

8

9

10

前序遍歷:

1, 2, 9, 5, 8, 7, 6, 10, 3, 4

性質:進入一個節點後,走完該子樹的所有節點之後才會離開。

他的用處:用序列表示樹

1

2

3

4

5

6

7

8

9

10

紀錄每個點的左界和右界,代表該點的子樹的區間。

int ti = 0;
int lef[maxn], rig[maxn]; //[lef, rig)
void dfs(int n, int par) {
    lef[n] = ti++;
    for (int v:adj[n]) {
        if (v != par) {
            dfs(v, n);
        }
    }
    rig[n] = ti;
}

考慮這種問題

樹上每個點上有一個數值aia_i

處理 qq 筆操作。

  1. 改變某個點的值
  2. 詢問以vv為子樹的數值和

變成線段樹!

在樹上歐拉遍歷 - 2

進去和離開點時都把點加到序列裡面,

也就是說序列長度為2n2n

1

2

3

4

5

6

7

8

9

10

進入:1, 2, 9,    5, 8,        7,         6, 10, 3,          4, 

離開:            9,        8, 5,    7, 2,               3, 10,    4, 6, 1

解決路徑相關的問題!

尋找根到任意節點的路徑

1

2

3

4

5

6

7

8

9

10

In\text{In}:1, 2, 9,    5, 8,        7,         6, 10, 3,          4, 

Out\text{Out}:         9,        8, 5,    7, 2,               3, 10,    4, 6, 1

考慮區間[in[1],in[v]][in[1], in[v]]

所有只出現一次inin的點都在(1,v)(1, v)的路徑上,ininoutout都出現的點應該被「抵銷」掉。

如果題目求的數值是可以用扣的(ex.路徑和),那可以將(u,v)(u, v)拆成

(1,u)+(1,v)2(1,lca(u,v))(1, u) + (1, v) - 2 * (1, lca(u, v))

還有一種樹壓平-3

在歐拉遍歷時每次遇到一個點就把他紀錄下來。

 

額外紀錄一個深度陣列

1

2

4

5

6

8

7

3

arrarr: 1, 2, 7, 7, 2, 5, 8, 8, 5, 2, 1, 6, 3, 3, 6, 4, 4, 6, 1

depdep: 0, 1, 2, 2, 1, 2, 3, 3, 2, 1, 0, 1, 2, 2, 1, 2, 2, 1, 0

用這種方法找路徑

1

2

4

5

6

8

7

3

arrarr: 1, 2, 7, 7, 2, 5, 8, 8, 5, 2, 1, 6, 3, 3, 6, 4, 4, 6, 1

紀錄每個點的首次出現(inin)和最後一次出現(outout)時間

對於兩個節點u,v (in[u]<in[v])u, v \ (in[u] < in[v])

1. vvuu的子樹內 (ex. 1-8)

[in[u],in[v]][in[u], in[v]]

2. vv 不在uu的子樹內 (ex. 5-4)

[out[u],in[v]][out[u], in[v]]

用樹壓平做O(nlogn)/O(1)O(n \log n)/ O(1) LCA

一樣定義in[v],out[v]in[v], out[v]分別為點vv第一個、最後一個出現在序列的位置

1

2

4

5

6

8

7

3

arrarr: 1, 2, 7, 7, 2, 5, 8, 8, 5, 2, 1, 6, 3, 3, 6, 4, 4, 6, 1

depdep: 0, 1, 2, 2, 1, 2, 3, 3, 2, 1, 0, 1, 2, 2, 1, 2, 2, 1, 0

Sparse Table!

對於兩個點u,vu, vlca(u,v)lca(u, v)會位於

in[u],in[v]in[u], in[v]中間,且in[u],in[v]in[u], in[v]必定在[in[lca(u,v)],out[lca(u,v)]][in[lca(u, v)], out[lca(u, v)]]中間

也就是說lca(u,v)lca(u, v)[in[u],in[v]][in[u], in[v]]中間深度最小的一個!

以下是練習題

大家自己判斷要怎麼樹壓平!

給你一棵樹,每個點上有值viv_i,對於每個點uu,找出他子樹內有幾個點vv值比uu大。

 

n105,vi109n \leq 10^5, v_i \leq 10^9

給你一棵樹,每個點上有值aia_i,有qq筆詢問,每次問兩個點u,vu, v的路徑間的xorxor和。

 

n,q105,ai109n, q \leq 10^5, a_i \leq 10^9

給一棵有根樹,上面每個點是 1 或 0 (一開始全部是0),接下來有qq筆操作。

 

1. 改一個子樹裡面每個點的值 (0變1, 1 變 0)

2. 詢問一個點的值

n,q105n, q \leq 10^5

給你兩棵有nn個節點的樹T1,T2T_1, T_2

請找出有多少對邊e1T1,e2T2e_1 \in T_1, e_2 \in T_2,使得當我們分別把這兩條邊拔掉後,兩棵樹上所製造的兩個連通塊點集可以一一對應。

1

2

2

3

3

5

4

1

4

5

如左圖有三組解:

n105n \leq 10^5

註:想想看確定性(不用random/hash)的解

例題5:數樹問題

樹DP/樹分治

在樹上DP有什麼特別的?

  • 通常需要考慮不定數量個轉移點(例如子節點數量)
  • 有根樹 vs 無根樹
  • 有時候他根本不是DP,而是分治或Greedy
  • 有不同的應用/變形

樹DP常見的想法:

把根拔掉,拆成一棵棵的子樹

簡單的例題:樹上最大匹配

給定一棵樹,選出一個邊的集合SS,使得SS裡面任兩條邊都沒有共用點,並且S|S|盡量大。

 

O(n)O(n)

1

2

3

4

5

6

7

8

9

10

解一:DP

dp[0][v]dp[0][v]代表以vv為根的子樹中,不把vv連到子節點的最大邊數。

 

dp[1][v]dp[1][v]代表把vv連到其中一個子節點的最大邊數。

dp[0][v]=max(dp[0][u],dp[1][u])dp[0][v] = \sum max(dp[0][u], dp[1][u])

dp[1][v]dp[1][v]要怎麼辦?

解一:DP

所有子節點uu中,至少要有一個使用dp[0][u]dp[0][u],其他的可以用max(dp[0][u],dp[1][u])max(dp[0][u], dp[1][u])

 

也就是說:

dp[1][v]=(max(dp[0][u],dp[1][u]))min(max(dp[0][u],dp[1][u])dp[0][u])+1dp[1][v] = (\sum max(dp[0][u], dp[1][u])) \\ - min(max(dp[0][u], dp[1][u]) - dp[0][u]) + 1
dp[1][v] = (\sum max(dp[0][u], dp[1][u])) \\ - min(max(dp[0][u], dp[1][u]) - dp[0][u]) + 1

uuvv配對所付出的「代價」

解二:其實Greedy就好了

從葉節點開始選,如果可以配對就配對,並把配對的兩個節點拔掉。

1

2

3

4

5

6

7

8

9

10

為什麼是對的?

選那一條邊的答案 \geq不選

(如果不選就等同於那個葉節點不存在)

其實樹DP大概就這樣

但他超難><

 

以下的題目會有某些 OI 題的雷

給你一棵樹,每個節點上面有一個人,現在每個人都必須去一個不同的節點,而且每個節點最後只能有一個人,問所有人移動距離的最小總和(並輸出移動方法)。

 

n105n \leq 10^5

給你一棵樹,每個節點是黑色或白色,問有多少種方法把樹分成任意多個連通塊,使得每個連通塊內洽有一個黑點。

(答案模109+710^9 + 7)

 

n105n \leq 10^5

給你一棵nn個節點的樹和距離DD,請選出最多個點,使得任兩點的距離至少為DD

全方位木DP

なに

在大部份的樹DP問題,我們都可以直接選根

但有時候我們必須考慮所有可能的根

要怎麼有效率的做這件事呢?

來看一個裸題

在一棵樹上,點有點權aia_i,請找出一個點vv,使得1indist(i,v)ai\sum_{1 \leq i \leq n} dist(i, v) \cdot a_i 最大

 

n2×105n \leq 2 \times 10^5

O(n2)O(n^2)顯然可做,但是要怎麼優化?

優化暴力?

先隨便選一個點DFS,用一個變數紀錄答案,並對每個點存兩個值:

  • 子樹大小
  • 子樹內的aia_i總和

當我要從目前的根uu移動到相鄰的點vv為根時...

u

v

4

5

6

7

8

9

10

只會改到u,vu, v的資訊!

O(1)O(1)更新

並維護答案

我們做得到的事

O(1)O(1)內把根往一條邊移動

這樣就夠了

對樹做歐拉遍歷,每經過一條邊就改變根的位置,總共改變O(n)O(n)次,而且每個點都會被看到。

 

這就是全方位木DP,又稱Rerooting Technique

例題1: TIOJ 魔法鏈

給你一棵樹,你每次可以選一個葉子(只有連到一個點)的節點,把他的編號紀錄在一個序列,並拔掉那個點。問可以形成的序列數。

 

註:本題的n2000n \leq 2000,但其實可以做到n2×105n \leq 2 \times 10^5

2

5

7

8

9

合法序列有:

9, 8, 5, 7, 2

7, 8, 5, 2, 9

...

不合法序列有:

9, 5, 8, 7, 2

 

給你一棵樹,點有點權aia_i,一個「好的」根節點vv會符合,對於任意一條從vv到點uu的路徑,路徑上都不會有權值相同的點。問有幾個好的根節點。

 

n2×105n \leq 2 \times 10^5

Binary Jumping

倍增法

還記得倍增法的LCA怎麼做嗎?

對每個點維護他的1,2,4,...,2k1, 2, 4, ..., 2^k倍祖先,即可以在O(logn)O(\log n)時間內找到一個點的xx倍祖先。

其實,這個技巧不一定要在樹上也可以做。

一個常見的變形是:一個點指向另一個(可能有環)

這題:Lynyrd Skynyrd

你有一個 1~n 的排列pp和一個長度為mm的序列aa

保證1ain1 \leq a_i \leq n

接著有qq筆詢問,每次問區間[l,r][l, r],回答aa裡面是否存在一個在[l,r][l, r]內的子序列,是排列pp的某個循環移位 (Cyclic shift)

 

n,m,q2×105n, m, q \leq 2 \times 10^5

假設我固定子序列的第一項,如果他是答案的話,考慮最後一項....

提示->

倍增法還可以做什麼

把一條路徑分成logn\log n條不重疊的路徑!

當我們在處理可合併的東西的時候(ex. min, max, sum, gcd, ...),在做倍增表的時候就可以一邊紀錄這個東西,並用相同的方法在O(logn合併時間)O(\log n * 合併時間)找答案。

相信大家都有看過這一類題目了:

https://ojdl.ck.tp.edu.tw/problem/7110

初選pD (Work in progress)

LCA 的一個(有點廢話的)性質

  • 每一對點會有恰好一個LCA,因此可以用枚舉LCA的方式枚舉路徑

  • 其實我原本要寫更多性質的但我想不到了

給你一張圖,邊有邊權,對於每一條邊,找到包含那條邊的最小生成樹權值。

 

n,m2×105n, m \leq 2 \times 10^5,圖連通

例題:Root LCA Queries

給你一棵樹,有qq筆詢問,每次問三個點a,b,ca, b, c,回答有多少點dd,使得如果你把dd當根節點,lca(a,b)=clca(a, b) = c

 

n,q105n, q \leq 10^5

補充:強強的LCA

使用O(n)O(n)記憶體和預處理時間,一樣O(logn)O(\log n)kk倍祖先

然後其實倍增表能做到的所有事情他都可以做到(因為路徑一樣可以被拆解)。

Small to large Merging

啟發式合併

啟發式合併?

簡單來說,就是當我們要合併兩個大小為a,b (ab)a, b \ (a \geq b)的資料結構(可以是序列,set,map...)時,把小的合併到大的。

他跟樹上問題的關係?

1

2

3

4

5

6

7

8

9

10

假設我對一個子樹必須維護O(子樹大小)O(子樹大小)的東西

 

那在合併子樹答案的時候就可以用這個方法。

 

他的複雜度是...?

什麼?多一個 log 而已

nn個點,每次合併大小為a,ba, b的連通塊時需要O(min(a,b))O(min(a, b))的時間。

 

考慮一個點所在的連通塊大小,每次他「被合併到其他連通塊」時,這個大小會增加為原本的至少22倍,也就是說一個點只會被算到O(logn)O(\log n)次。

 

nn個點,所以總共合併 O(nlogn)O(n \log n)

例題:Lomsat Gelral

給你一棵有根樹,每個點上有顏色cic_i,一個子樹內的顏色為「重要」的,代表沒有其他顏色在那個子樹內出現比該顏色更多次(也就是說可能有多個重要的顏色)。

 

對於每個點,找到該子樹所有重要顏色的編號(cic_i)總和。

 

n105n \leq 10^5

 給你一棵有根樹以及預算上限MM,每個點上是一個忍者,有出動費用cic_i和領導值lil_i,如果你選了vv為領導人,則他的滿意度為

lv×預算內能在v子樹內出動的忍者數量l_v \times 預算內能在v子樹內出動的忍者數量

 

請找出最大的滿意度。

 

n105,M,ci,li109n \leq 10^5, M, c_i, l_i \leq 10^9 

 給你一棵nn個葉節點的二元樹,每個葉子上有一個值aia_i,且所有aia_i形成一個排列。

你可以選擇任意個非葉節點並把他們的左右小孩交換。在交換之後,我們用前序遍歷這棵二元樹並上葉節點上的值紀錄成一個序列,

求這個序列最少的逆序數對數。

 

n2×105n \leq 2 \times 10^5

Heavy Light Decomposition

樹鏈剖分

Binary Jumping 的問題

一個點會被O(n)O(n)個區間覆蓋到,所以如果點上要修改,會有O(n)O(n)個值改變。

 

我們需要把樹上路徑切成O(logn)O(\log n)條不相交的路徑,而且每個點都只被一個紀錄的路徑覆蓋到。

所以就有樹鏈剖分了

每個點都屬於一條樹鏈

考慮點vv所在的樹鏈d(v)d(v)和他所有的小孩uu

子樹大小最大的一個小孩會有d(u)=d(v)d(u) = d(v)

其他的小孩屬於一條新的樹鏈。

這樣的複雜度

定義up(v)up(v)為點vv所在樹鏈的「頂端」的節點。

那考慮一個點vv走到根的路徑中,依序經過的樹鏈頂端的子樹大小。

 

跳到另一個樹鏈時,樹鏈頂端的子樹大小至少會加倍。

至多只會經過O(logn)O(\log n)條樹鏈

那樹鏈剖分之後呢?

任意一條路徑可以被拆成O(logn)O(\log n)條樹鏈組成的路徑

把一條樹鏈當成序列的一個區間,一條路徑會變成O(logn)O(\log n)個區間,可以用線段樹維護。

那我們就會做這種問題

給你一棵樹,點有點權aia_i,處理qq筆操作。

  1. 把點vv的值改成xx
  2. 詢問uuvv路徑的最大aia_i

 

n,q105n, q \leq 10 ^5

More (hard) Problems

 

以下有很多 OI 題

很多我曾經會的題

跟很多我不會的題

卡常

在一個nn個點的有根樹上,每個點有顏色1ciK1 \leq c_i \leq K,接著有qq筆操作:

 

1. 把一個點的顏色改為xx

2. 詢問以vv為根的子樹有多少種不同的顏色

 

n5×105,q3×105,SLOWJUDGEn \leq 5 \times 10^5, q \leq 3 \times 10^5, SLOW JUDGE

這題有兩種解法w

Made with Slides.com