學習筆記 樹分治

2021-07-23 11:53:09 字數 4371 閱讀 9897

樹分治用於解決有關路徑的問題。

樹分治分為點分治和邊分治(其實還有一種叫「鏈分治」,是樹的路徑剖分思想的更高階的體現,一般鏈分治的題目都可以用路徑剖分解決)。點分治就是每次找到重心,然後把重心去掉,對分成的每兩棵樹之間分別統計路徑資訊(以重心的每個相鄰點為根,遍歷整棵子樹即可得到這個根到每個結點的統計資訊),就可以知道包含這個重心的所有路徑的資訊,然後對於剩下的路徑就是在子樹裡面進行同樣的操作了,直到只剩乙個點為止(注意這乙個點所構成的路徑有時也要處理一下)。邊分治就是每次找到一條邊,使得刪掉這條邊後分成的兩棵子樹大小盡可能平均,然後以刪掉的邊的兩端點為根,分別統計根到兩棵樹中的每個結點的路徑資訊,最後合併算路徑,即可得到包含這條邊的所有路徑的資訊,剩下的路徑在兩棵樹中遞迴處理。

點分治和邊分治是可以通用的,不管樹上的資訊記錄在結點上還是邊上。這時乙個很囧的問題就出現了:這兩種分治到底哪個好?這也是本總結討論的重點。

有關「哪個好」的問題顯然要從三種複雜度上考慮。由於點分治和邊分治的空間複雜度均為o(n),因此討論空間複雜度木有意義。所以需要比較的就是時間複雜度和程式設計複雜度了。

先說時間複雜度。點分治每次找到重心後,需要將重心刪掉然後分成很多棵子樹,對每兩棵子樹之間都要統計一下。如果操作是可反的(比如計數問題:統計某種路徑的條數),可以通過先將所有子樹合在一起統計,再減去兩端點處在相同子樹內的路徑,但是,如果操作不可反(比如找「最優路徑」),就不能這樣做了,只能列舉所有的子樹對。顯然,如果有s棵子樹,則有o(s

2)個子樹對,最壞情況下(星形的樹),s=n-1,此時一一枚舉子樹對必然會導致總時間複雜度公升到o(n

2)以上。當然這是有解決辦法的,可以從小到大枚舉子樹,每處理完一棵子樹就將其和前面的合併,此時演算法效率取決於合併的方法。如果使用歸併法(大小為a的和大小為b的合併次數為a+b),對於星形樹的合併總次數仍然為o(n

2),如果能保證大小為a的和大小為b的合併次數為min,則可以保證合併總次數在o(n)以內,從而可以保證總時間複雜度為o(nlogn)。邊分治由於分成的永遠是兩棵子樹,所以在處理子樹之間的關係上能夠保證o(n),問題是它並不能保證每次分成的兩棵子樹大小非常平均,在最壞情況下(星形的樹),不管刪哪條邊分成的兩棵子樹大小都是乙個1乙個(n-1),這樣就會使總的時間複雜度上公升為o(n

2)。從以上的討論中可以看出,點分治和邊分治最怕的都是星形的樹,也就是那種某個點的度數特別大的樹。相關**中提到一種辦法通過加無用點的方式將其轉化為二叉樹,從而解決這個問題。這樣一來,點分治和邊分治的時間複雜度都可以保證了。接下來是程式設計複雜度的問題:點分治需要找重心(三次dfs或bfs),刪掉重心(需要刪點),且分成最多三棵樹,需要處理三對關係,**量肯定大一些,而邊分治只需要算出子樹大小後就可以找到最優邊,並且分成的是兩棵樹,更重要的是,在有根樹上刪掉一條邊後,分成的兩棵樹中有一棵已是有根樹,另一棵可以通過扭根的方式將其轉化為有根樹,由於二叉樹扭根是很方便的,所以就不需要每次重新建樹,**量較小。綜合以上因素,邊分治更好些。

另外,其實那種加無用點轉化為二叉樹的方式是有問題的,因為它改變了路徑的意義,可能導致一條路徑由於lca是無用點而在統計時出現錯誤(明明是跨越了重心或最優邊的路徑被當成木有跨越的),但是……這個問題有一種很簡單的解決辦法,想知道是什麼……ac了aloext再告訴你……

e.g. 

(2023年候選隊互測•a142857a) 樹

邊分治,每次找到所有點到根路徑的xor和,以及特殊點的個數,按照特殊點個數排序後有序插入trie,查詢即可。

**:

1 #include 2 #include 3 #include 4 #include 

5 #include 6

using

namespace

std;

7#define re(i, n) for (int i=0; i8

#define re1(i, n) for (int i=1; i<=n; i++)

9#define re2(i, l, r) for (int i=l; i10

#define re3(i, l, r) for (int i=l; i<=r; i++)

11#define rre(i, n) for (int i=n-1; i>=0; i--)

12#define rre1(i, n) for (int i=n; i>0; i--)

13#define rre2(i, r, l) for (int i=r-1; i>=l; i--)

14#define rre3(i, r, l) for (int i=r; i>=l; i--)

15#define ll long long

16const

int maxn = 200010, maxs = 31, inf = ~0u >> 2;17

struct

edge e[maxn << 1

];20

struct

sss

23} s1[maxn], s2[maxn];

24int t[maxn * maxs][2

];25

int n, m0, k, sum0, __no, n, a[maxn], q[maxn], ls[maxn], l[maxn], r[maxn], pr[maxn], sz[maxn], v1[maxn], v2[maxn], res = -1;26

bool

b[maxn], vst[maxn];

27void

init_d()

2831

void add_edge(int a, int

b)32

36void

init()

3741 re(i, n) scanf("

%d", &a[i]);

42 re2(i, 1, n) 43}

44int dfs0(int l, int

r)4551}

52void

prepare()

5360}61

if (sum0 > 1) else

if (sum0 == 1) else l[x] = r[x] = -1;62

}63}64

void solve(int no, int

n0)65

70int x, _x, y, minv = inf, _n0 = n0 >> 1; q[0] =no;

71for (int front=0, rear=0; front<=rear; front++)

76rre(i, n0) 81}

82 x = pr[_x]; pr[_x] = -1; if (l[x] == _x) l[x] = -1; else r[x] = -1;83

int sum0 = 1; ls[0] = x; while ((y = pr[x]) != -1)

84 pr[ls[0]] = -1; re2(i, 1, sum0) pr[ls[i]] = ls[i - 1

];85

int len1 = 0, len2 = 0

;86 q[0] = _x; v1[_x] = b[_x]; v2[_x] = a[_x]; s1[len1].v1 = v1[_x]; s1[len1++].v2 =v2[_x];

87for (int front=0, rear=0; front<=rear; front++)

90if ((y = r[x]) >= 0) 91}

92 q[0] = ls[0]; v1[ls[0]] = b[ls[0]]; v2[ls[0]] = a[ls[0]]; s2[len2].v1 = v1[ls[0]]; s2[len2++].v2 = v2[ls[0

]];93

for (int front=0, rear=0; front<=rear; front++)

96if ((y = r[x]) >= 0) 97}

98 sort(s1, s1 + len1); sort(s2, s2 + len2); int _len2 = len2 - 1

;99 n = 1; t[1][0] = t[1][1] = 0; int

v0, _v;

100re(i, len1)

106 x =t[x][y];

107}

108}

109if (t[1][0] || t[1][1

]) else x =t[x][y];

114}

115if (_v > res) res =_v;

116}

117}

118 x = _x; y = ls[0

];119

solve(x, len1); solve(y, len2);

120}

121void

pri()

122125

intmain()

126

view code

學習筆記 樹分治

樹分治用於解決有關路徑的問題。樹分治分為點分治和邊分治 其實還有一種叫 鏈分治 是樹的路徑剖分思想的更高階的體現,一般鏈分治的題目都可以用路徑剖分解決 點分治就是每次找到重心,然後把重心去掉,對分成的每兩棵樹之間分別統計路徑資訊 以重心的每個相鄰點為根,遍歷整棵子樹即可得到這個根到每個結點的統計資訊...

學習筆記 線段樹分治

線段樹分治是一種離線分治演算法,主要思想是建立以詢問的時間為葉節點的線段樹,考慮修改對詢問的影響 修改會影響一段時間上的詢問 用類似標記永久化的思想進行區間修改 即把原修改影響的區間 l,r 分成最多 log r l 1 段,將操作掛在對應的節點上,不上傳也不用下傳 最後 dfs 一遍,每進入乙個節...

線段樹分治學習筆記

參考部落格1 參考部落格2 核心思想 適用範圍 遇到如下問題 例題 p5787 二分圖 模板 線段樹分治 對時間軸建立線段樹。維護區間內存在的邊集,用 vec to rvector vector 進行維護。查詢時從根節點出發開始遍歷,遞迴處理時將當前節點存在的邊進行合併,判斷是否為二分圖。若到達某個...