[Link-Cut-Tree]【學習筆記】

Candy?發表於2017-01-10

可以按照<Utopiosphere>的調唱出來 “Link-Cut ,Time doesn’t stop .Prepare your doubts ,Eat them up”

 

參考資料:

1.popoqqq課件

2.《QTREE 解法的一些研究 》

3.http://blog.csdn.net/clove_unique/article/details/50991804

 

【理論知識】

  • Link-Cut-Tree(簡稱LCT)是解決動態樹類問題一種資料結構
  • Preferred Child:重兒子,重兒子與父親節點在同一棵Splay中,一個節點最多隻能有一個重兒子
  • Preferred Edge:重邊,連線父親節點和重兒子的邊
  • Preferred Path :重鏈,由重邊及重邊連線的節點構成的鏈

 

Auxiliary Tree(輔助樹)

  • 由一條重鏈上的所有節點所構成的Splay稱作這條鏈的輔助樹
  • 每個點的鍵值為這個點的深度,即這棵Splay的中序遍歷是這條鏈從鏈頂到鏈底的所有節點構成的序列
  • 輔助樹的根節點的父親指向鏈頂的父親節點,然而鏈頂的父親節點的兒子並不指向輔助樹的根節點
  • (也就是說父親不認輕兒子只認重兒子,兒子都認父親)
  • 這條性質為後來的操作提供了依據

原樹與輔助樹的關係

  • 原樹中的重鏈 -> 輔助樹中兩個節點位於同一棵Splay
  • 原樹中的輕鏈 -> 輔助樹中子節點所在Splay的根節點的father指向父節點
  • 注意原樹與輔助樹的結構並不相同
  • 輔助樹的根節點≠原樹的根節點
  • 輔助樹中的father≠原樹中的father

輔助樹是不斷變化的,重鏈和輕鏈不斷變化

 

二【實現】

LCT用到的Splay和通常的還是有點不同,沒有權值v,不進行查詢操作,點編號就是原樹的編號

因為是一個Splay森林,多條重鏈多個根,所以用isRoot(x)判斷是否為根,判斷isRoot(x)相當於判斷x的父親存不存在

rotate只是設定g的兒子時判斷isRoot(f)就行了

splay需要pushDown了(因為沒有kth了),也是判斷isRoot(pa)

Access和Cut更新了兒子關係,所以需要update

 

Access 

  • 將一個點與原先的重兒子切斷,並使這個原樹上點到根路徑上的邊全都變為重邊
  • 所以 這個節點到根的路徑上的所有節點形成了一棵Splay
  • 便於操作或查詢節點到根路徑上的所有節點

實現:不斷把x splay到當前Atree的根,然後它的右子樹就是重兒子了,修改;用y輔助

注意:Access後x不一定為這顆Splay的根,因為中途x變fa了

維護了節點資訊別忘更新

MakeRoot

  • 將x設為原樹的根

實現:Access後splay到根,然後全在x的左子樹上(權值是深度),區間翻轉即可

FindRoot

  • 找x所在原樹根,判連通性

實現:MakeRoot後不斷往左找(不需要pushDown?加上也可以啊。不加也對因為只是來判連通,判斷是不是在一棵原樹上,都不pushDown找到的還是同一個點吧)

Link

實現:MakeRoot(x)然後t[x].fa=y

Cut

實現:MakeRoot(x)然後Access(y) splay(y) ,x就在y的左兒子了,t[y].ch[0]=t[x].fa=0;

維護了節點資訊別忘更新

對x到y路徑上的點進行修改或查詢
只需要對x進行Move_To_Root操作,然後對y進行Access+Splay操作,那麼x到y路徑上的所有點都在以y為根的子樹上

因為Access後x和y重鏈在一棵Splay上,x深度比y小

 

三【模板】

[update 2017-04-05]

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define lc t[x].ch[0]
#define rc t[x].ch[1]
#define pa t[x].fa
typedef long long ll;
const int N=3e5+5;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

namespace lct {
    struct meow{int ch[2], fa, rev, sum, w;} t[N];
    inline int wh(int x) {return t[pa].ch[1] == x;}
    inline int isr(int x) {return t[pa].ch[0] != x && t[pa].ch[1] != x;}
    inline void update(int x) {t[x].sum = t[lc].sum ^ t[rc].sum ^ t[x].w;}
    inline void rever(int x) {t[x].rev ^= 1; swap(lc, rc);}
    inline void pushdn(int x) {
        if(t[x].rev) {
            if(lc) rever(lc);
            if(rc) rever(rc);
            t[x].rev = 0;
        }
    }
    void pd(int x) {if(!isr(x)) pd(pa); pushdn(x);}
    inline void rotate(int x) {
        int f=t[x].fa, g=t[f].fa, c=wh(x);
        if(!isr(f)) t[g].ch[wh(f)]=x; t[x].fa=g;
        t[f].ch[c] = t[x].ch[c^1]; t[t[f].ch[c]].fa=f;
        t[x].ch[c^1] = f; t[f].fa=x;
        update(f); update(x);
    }
    inline void splay(int x) {
        pd(x);
        for(; !isr(x); rotate(x))
            if(!isr(pa)) rotate( wh(pa)==wh(x) ? pa : x );
    }

    inline void access(int x) {
        for(int y=0; x; y=x, x=pa) splay(x), rc=y, update(x);
    }
    inline void maker(int x) {
        access(x); splay(x); rever(x);
    }
    inline int findr(int x) {
        access(x); splay(x);
        while(lc) pushdn(x), x=lc; return x;
    }
    inline void link(int x, int y) {
        maker(x); t[x].fa=y;
    }
    inline void cut(int x, int y) {
        maker(x); access(y); splay(y);
        t[x].fa = t[y].ch[0] = 0; update(y);
    }
    inline void split(int x, int y) {
        maker(x); access(y); splay(y);
    }
} using lct::findr;

int n, Q, op, x, y;
int main() {
    freopen("in","r",stdin);
    n=read(); Q=read();
    for(int i=1; i<=n; i++) lct::t[i].w = read();
    for(int i=1; i<=Q; i++) {
        op=read(); x=read(); y=read();
        if(op==0) lct::split(x, y), printf("%d\n", lct::t[y].sum);
        if(op==1) if(findr(x) != findr(y)) lct::link(x, y);
        if(op==2) if(findr(x) == findr(y)) lct::cut(x, y);
        if(op==3) lct::t[x].w = y, lct::splay(x);
    }
}
View Code

 

 

 

四【一點好玩的東西】

1.LCT可做動態樹問題

2.LCT可做樹鏈剖分

3.LCT可做支援刪除邊的並查集(我太navie了.......並不能完全實現這個功能,是一顆樹啊啊啊)

4.LCT可做不用排序的Kruskal(動態加邊的最小生成樹)