1. 程式人生 > >link-cut-tree 簡單介紹

link-cut-tree 簡單介紹

目錄

前言:這個演算法似乎機房全都會,就我不會了TAT...強行搞了很久,勉強照著別人程式碼抄了一遍qwq

這個本人看論文實在看不懂,太菜了啊!!! 只好直接看如何實現...可是實現也看不太懂...

但直到我看到一篇大佬部落格!!! point here 是真的講的好,一點都不敷衍.真沒收錢

本篇比較乾貨qwq(沒圖啊!!!) 一定要耐住性子學演算法!!!

概念辨析

  • 但在之前還是要辨析幾個概念:

輔助樹

本人理解就是對於原樹抽象出一個splay 或者說很多棵splay (啥,你告訴我原樹是多叉樹,splay只是一個二叉樹)

其實就是將一個splay劃分成很多部分(每一部分維護原圖中的一條鏈) 然後對於這些部分連輕邊.

(建議先認真看看那些基礎概念) 然後對於一條鏈來說,深度兩兩不同,我們就可以用這個作為splay的鍵值來排序

然後這就可以維護出原樹了... 為什麼呢? 因為 雖然父親只能有兩個兒子,但是有很多兒子來認他啊qwq

(就像造樹的時候只要給每個點一個fa就行了) 然後每條鏈又在splay中可以確定他們的先後順序(也就是深度大小)

輕邊和重邊

這個不像樹剖,這個就是隨便給的(根據操作access來改變) 然後重邊存在於splay中的子認父也認 的邊, 也就是維護鏈時候的邊. 然後輕邊,就是隻連到父親時子認父不認 的邊,就是一顆splay連到重鏈頂端父親的那條邊,而且一個點只能有一個重邊!

操作介紹

然後就直接介紹操作吧(你還是沒聽懂?那看看別人部落格的概念介紹吧qwq)

access

就是拉鍊,就是把一個點到根節點路徑全都變為重邊,且它是這條路徑的一個端點.

如何實現呢qwq... 就是每次把當前這個節點splay到根,然後這個splay的父親認了他這個兒子就行了,

接下來上傳資訊... 不斷操作,直到到根 (注意要把他變為樹的一個端點,所以要一開始要將它的兒子認為空的)

access=splay+child+push_up

make_root

使得一個點變為原樹的根,然後這就可以便於維護路徑資訊了.

首先先把這個點access到根,之後將這個點splay到當前splay的根,然後再下放一個rev

的標記就行了.

這是因為你使當前重鏈的深度完全翻轉了(注意,我們splay是按照深度排序的!!!)

x就變為深度最小的點(即根節點)

make_root=access+splay+rev

find_root

找到原樹的根,這個就易於判斷連通性了(類似於並查集)

又是access到根,然後也是splay到根. (這樣似乎很好維護一些東西,而且更方便去想了)

然後直接一直向左邊走,最下面那個就是根節點了(深度最小, 時間複雜度因為有雙旋所以可以保證)

而且在走左子樹的時候要push_down!! (大佬部落格上講的,我還沒被坑過qwq)

find_root=access+splay+go_left_child

split

將原圖中的一條路徑變為一條以它們為端點的重鏈.

假設我們split(x,y). 首先先把make_root(x)便於操作. 然後access(y),拉一條路徑出來.

為了查詢資訊我們splay(y)splay的根上去,直接訪問y的資訊(中間splaypush_up)

split(x,y)=make_root(x)+access(y)+splay(y)

將原圖中的兩個點連一條邊

我們把link(x,y)定義為把x的父親認做y. (其實你互換也是可以的)

如果操作合法,我們只要make_root(x)然後x的父親認做y就行了.

就是讓x變為他所在樹的根就行了qwq.. 不合法的話,就判斷聯通性就行了.

link(x,y)=make_root(x)+father[x]=y

cut

將原圖中的一條邊斷掉

我們同樣把cut(x,y)定義為把原圖中x與其父親y的邊斷掉. (同上互換也是可以的)

這個合法的話,直接把他們中的那條鏈split出來,直接斷掉就行了...

不合法的話,同樣嘗試split出來,如果x的左兒子不是y就不行.

這樣意義就是y在原樹中不是x的父親,不能cut掉.

cut(x,y)=split(x,y)+left_child[y]=fa[x]=0

至此link_cut_tree所有基礎知識已經講完qwq...然後高階操作只能自己做題了...

細節問題

這個lct細節是真的多,一不小心就調不出來了啊!!!

  1. rotate和普通的不同,要判斷是不是當前splay的根!!!!

而且連邊的時候一定要注意順序啊!!!

就是判斷is_root(u)之前不能連u-v的那條邊!!!!

  1. splay那裡也是 判斷is_root的時候要注意物件啊,是fa[u]!!!

  2. make_root那裡不能先交換一遍!!!直接打標記在那裡就行了!!!

(有些人寫法是先交換,再打左右兒子標記,我程式碼不同啊!!!)

  1. splayt要從o開始,然後判斷!is_root(t)而不能從fa[o]開始,這是因為fa[o]可能為\(0\)啊!!!

  2. rotate中是push_up而不是push_down,前面splaypush_down完了啊!!!

程式碼

(沒有註釋....湊合對對,這道題是luogu模板)

#include <bits/stdc++.h>
#define debug cerr << "Pass" << endl
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * fh;
}

void File() {
#ifdef zjp_shadow
    freopen ("P3690.in", "r", stdin);
    freopen ("P3690.out", "w", stdout);
#endif
}

const int maxn = 1e6 + 1e3;

int val[maxn];
#define ls(u) ch[u][0]
#define rs(u) ch[u][1]
struct Link_Cut_Tree {
    int fa[maxn], ch[maxn][2], rev[maxn], xsum[maxn];

    inline bool is_root(int o) { return o != ls(fa[o]) && o != rs(fa[o]); }

    inline bool get(int o) { return o == rs(fa[o]); }

    inline void push_up(int o) { xsum[o] = xsum[ls(o)] ^ xsum[rs(o)] ^ val[o]; }

    inline void push_down(int o) {
        if (rev[o]) { swap(ls(o), rs(o)); rev[ls(o)] ^= 1; rev[rs(o)] ^= 1; rev[o] = 0; }
    }

    inline void rotate(int v) {
        int u = fa[v], t = fa[u], d = get(v);
        fa[ch[u][d] = ch[v][d ^ 1]] = u;
        fa[v] = t; if (!is_root(u)) ch[t][rs(t) == u] = v;
        fa[ch[v][d ^ 1] = u] = v;
        push_up(u); push_up(v);
    }

    int sta[maxn], top;
    inline void splay(int u) {
        sta[top = 1] = u;
        for (register int t = u; !is_root(t); t = fa[t]) sta[++ top] = fa[t];
        while (top) push_down(sta[top --]);
        for (; !is_root(u); rotate(u)) if (!is_root(fa[u])) rotate(get(u) ^ get(fa[u]) ? u : fa[u]);
    }

    inline void access(int o) { for (int t = 0; o; o = fa[t = o]) splay(o), rs(o) = t, push_up(o); }

    inline void make_root(int o) { access(o); splay(o); rev[o] ^= 1; }

    inline int find_root(int o) { access(o); splay(o); while (ls(o)) o = ls(o), push_down(o); splay(o); return o; }

    inline void split(int v, int u) { make_root(v); access(u); splay(u); }

    inline bool link(int v, int u) { make_root(v); if (find_root(u) == v) return false; fa[v] = u; return true; }

    inline void cut(int v, int u) { split(v, u); if (ls(u) == v) ls(u) = fa[v] = 0; }
} lct;

int n, m;

int main () {
    File();
    n = read(); m = read();
    For (i, 1, n) val[i] = lct.xsum[i] = read();
    For (i, 1, m) {
        int opt = read(), x = read(), y = read();
        if (!opt) { lct.split(x, y); printf ("%d\n", lct.xsum[y]); } 
        else if (opt == 1) lct.link(x, y);
        else if (opt == 2) lct.cut(x, y);
        else { lct.access(x); lct.splay(x); val[x] = y; lct.push_up(x); }
    }
    return 0;
}