1. 程式人生 > 其它 >動態樹問題,Link Cut Tree 學習筆記

動態樹問題,Link Cut Tree 學習筆記

技術標籤:學習筆記

動態樹問題,Link Cut Tree 學習筆記

文章目錄

前言

我的學習資料:

FlashHu的部落格1

FlashHu的部落格2

oi-wiki——Link Cut Tree

這幾篇部落格都很經典。

動態樹問題

同樣是維護樹,動態樹比樹鏈剖分更加困難。它多了兩種操作:Link(連邊),Cut(斷邊)。動態樹其實不是一種演算法,而是一種思想,我們要維護一個動態的森林。

OI中,我們常常用 LCT(Link-Cut Tree)來進行動態樹維護。

模板題引入

P3690 【模板】Link Cut Tree (動態樹)

維護一棵樹,點有點權,進行下列操作:

  1. 求路徑點權異或和。
  2. Link(連邊)
  3. Cut(斷邊)
  4. 單點修改點權。

保證任意時刻都是一棵樹。

這就是個經典的動態樹問題。

模板題,不會的同學請自行點選上述部落格進行學習。下面給出我的模板(壓行警告)。

時間複雜度應為 O ( m log ⁡ n ) O(m\log n) O(mlogn) m m m 次操作,每次 O ( log ⁡ n ) O(\log n) O(logn)

#include<cstdio>
#include<cstring>
#include
<algorithm>
using namespace std; typedef long long ll; char In[1 << 20], *ss = In, *tt = In; #define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++) ll read() { ll x = 0, f = 1; char ch = getchar(); for(; ch < '0' || ch > '9'
; ch = getchar()) if(ch == '-') f = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0'); return x * f; } const int MAXN = 1e5 + 5; int n, m, val[MAXN]; namespace LCT { #define ls ch[0] #define rs ch[1] struct Node {int ch[2], fa, rev, sum;}e[MAXN]; int nrt(int x) {return e[e[x].fa].ls == x || e[e[x].fa].rs == x;} int idy(int x) {return e[e[x].fa].rs == x;} void upd(int x) {e[x].sum = e[e[x].ls].sum ^ e[e[x].rs].sum ^ val[x];} void psdrev(int x) {if(x) swap(e[x].ls, e[x].rs), e[x].rev ^= 1;} void psd(int x) {if(e[x].rev) e[x].rev = 0, psdrev(e[x].ls), psdrev(e[x].rs);} void psdall(int x) {if(nrt(x)) psdall(e[x].fa); psd(x);} void rtt(int x) { int y = e[x].fa, z = e[y].fa, k = idy(x), s = e[x].ch[k^1]; e[x].fa = z; if(nrt(y)) e[z].ch[idy(y)] = x; e[y].fa = x; e[x].ch[k^1] = y; e[s].fa = y; e[y].ch[k] = s; upd(y); upd(x); } void splay(int x) { psdall(x); for(int y = e[x].fa; nrt(x); rtt(x), y = e[x].fa) if(nrt(y)) rtt(idy(x) == idy(y) ? y : x); } void access(int x) {for(int y = 0; x; y = x, x = e[x].fa) splay(x), e[x].rs = y, upd(x);} void mkrt(int x) {access(x); splay(x); psdrev(x);} int getrt(int x) {access(x); splay(x); while(e[x].ls) psd(x = e[x].ls); splay(x); return x;} void split(int x, int y) {mkrt(x); access(y); splay(y);} void link(int x, int y) {mkrt(x); if(getrt(y) != x) e[x].fa = y;} void cut(int x, int y) {mkrt(x); if(getrt(y) == x && e[y].fa == x && e[y].ls == 0) e[x].rs = e[y].fa = 0, upd(x);} } using namespace LCT; int main() { n = read(); m = read(); for(int i = 1; i <= n; i++) val[i] = read(); for(int i = 1; i <= m; i++) { int opt = read(), x = read(), y = read(); if(opt == 0) {split(x, y); printf("%d\n", e[y].sum);} else if(opt == 1) link(x, y); else if(opt == 2) cut(x, y); else { splay(x); val[x] = y; upd(x); } } return 0; }

LCT應用 & 技巧

接下來的題目基本選了FlashHu的部落格的題目(FlashHu yyds)。

型別1:維護路徑資訊

如果沒有 Link 和 Cut 操作,那麼其實可以用樹鏈剖分來完成。儘管 樹鏈剖分一般是 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),比 LCT 多了個 log ⁡ \log log,但是一般來說常數上樹剖完虐 LCT。

只要 split,再在 Splay 進行操作即可。

典型例題有

P3203 [HNOI2010]彈飛綿羊

P1501 [國家集訓隊]Tree II

型別2:維護連通性

動態樹上維護連通性相當簡單,用 getrt 操作得到兩個樹的根進行比對即可。

典型例題有

P3950 部落衝突

(未完待續)