動態樹問題,Link Cut Tree 學習筆記
阿新 • • 發佈:2021-01-25
技術標籤:學習筆記
動態樹問題,Link Cut Tree 學習筆記
文章目錄
前言
我的學習資料:
這幾篇部落格都很經典。
動態樹問題
同樣是維護樹,動態樹比樹鏈剖分更加困難。它多了兩種操作:Link(連邊),Cut(斷邊)。動態樹其實不是一種演算法,而是一種思想,我們要維護一個動態的森林。
OI中,我們常常用 LCT(Link-Cut Tree)來進行動態樹維護。
模板題引入
維護一棵樹,點有點權,進行下列操作:
- 求路徑點權異或和。
- Link(連邊)
- Cut(斷邊)
- 單點修改點權。
保證任意時刻都是一棵樹。
這就是個經典的動態樹問題。
模板題,不會的同學請自行點選上述部落格進行學習。下面給出我的模板(壓行警告)。
時間複雜度應為 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 進行操作即可。
典型例題有
型別2:維護連通性
動態樹上維護連通性相當簡單,用 getrt
操作得到兩個樹的根進行比對即可。
典型例題有
(未完待續)