動態樹之LCT(link-cut tree)講解


  動態樹是一類要求維護森林的連通性的題的總稱,這類問題要求維護某個點到根的某些數據,支持樹的切分,合並,以及對子樹的某些操作。其中解決這一問題的某些簡化版(不包括對子樹的操作)的基礎數據結構就是LCT(link-cut tree)。

  LCT的大體思想類似於樹鏈剖分中的輕重鏈剖分(輕重鏈剖分請移步http://www.cnblogs.com/BLADEVIL/p/3479713.html),輕重鏈剖分是處理出重鏈來,由於重鏈的定義和樹鏈剖分是處理靜態樹所限,重鏈不會變化,變化的只是重鏈上的邊或點的權值,由於這個性質,我們用線段樹來維護樹鏈剖分中的重鏈,但是LCT解決的是動態樹問題(包含靜態樹),所以需要用更靈活的splay來維護這里的“重鏈”(splay請移步http://www.cnblogs.com/BLADEVIL/p/3464458.html)。

定義:

  首先來定義一些量:

  access(X):表示訪問X點(之后會有說明)。

  Preferred child(偏愛子節點):如果最后被訪問的點在X的兒子P節點的子樹中,那么稱P為X的Preferred child,如果一個點被訪問,他的Preferred child為null(即沒有)。

  Preferred edge(偏愛邊):每個點到自己的Preferred child的邊被稱為Preferred edge。

  Preferred path(偏愛路徑):由Preferred edge組成的不可延伸的路徑稱為Preferred path。

這樣我們可以發現一些比較顯然的性質,每個點在且僅在一條Preferred path上,也就是所有的Preferred path包含了這棵樹上的所有的點,這樣一顆樹就可以由一些Preferred path來表示(類似於輕重鏈剖分中的重鏈),我們用splay來維護每個條Preferred path,關鍵字為深度,也就是每棵splay中的點左子樹的深度都比當前點小,右節點的深度都比當前節點

的深度大。這樣的每棵splay我們稱為Auxiliary tree(輔助樹),每個Auxiliary tree的根節點保存這個Auxiliary tree與上一棵Auxiliary tree中的哪個點相連。這個點稱作他的Path parent。

看一個例子

粗的邊是Preferred path。那么3-7這個Auxiliary tree中,Path parent為1節點,每個單獨的點單獨在一棵splay中。以上描述的幾個量可以存儲這棵樹,並且維護相應的信息。

操作:

  access(X):首先由於preferred path的定義,如果一個點被訪問,那么這個點到根節點的所有的邊都會變成preferred edge,由於每個點只有一個preferred child,所以這個點到根節點路徑上的所有的點都會和原來的preferred child斷開,連接到這條新的preferred path上。假設訪問X點,那么先將X點旋轉到對應Auxiliary tree的根節點,然后因為被訪問的點是沒有preferred child的,所以將Auxiliary tree中根節點(X)與右子樹的邊斷掉,左節點保留,將這個樹的path parent旋轉到對應Auxiliary tree的根節點,斷掉右子樹,連接這個點與X點,相當於合並兩棵Auxiliary tree,不斷地重復這一操作,直到當前X所在Auxiliary tree的path parent為null時停止,表示已經完成當前操作。

procedure access(x:longint);
var
    y                    :longint;
begin
    splay(x);//旋轉
    while father[x]<>0 do
    begin
        y:=father[x];
        splay(y);
        root[son[y,1]]:=true;//son為子節點son[x,0]代表左子結點,son[x,1]代表右子結點
        root[x]:=false;//當前點是否為對應Auxiliary tree的根節點
        son[y,1]:=x;
        update(y);//更新y點的信息
        splay(x);
    end;
end;

  find root(x):找到某一點所在樹的根節點(維護森林時使用)。只需要access(X),然后將X節點旋到對應Auxiliary tree的根節點,然后找到這個Auxiliary tree中最左面的點。

 

function find root(x:longint):longint;
begin
  access(x);
  splay(x);//將X旋轉到根節點
  exit(find(x,-maxlongint));//找到子樹中最左面的點
end;

 

  cut(x):斷掉X節點和其父節點相連的邊。首先access(X),然后將X旋轉到對應Auxiliary tree的根節點,然后斷掉Auxiliary tree中X和左節點相連的邊。

procedure cut(x:longint);
begin
  access(x);
  splay(x);//旋轉x點到根節點
  father[son[x,0]]:=0;
  root[son[x,0]]:=true;//設置左子樹根節點
  son[x,0]:=-1;
end;

  link(join)(x,y):連接點x到y點上。即讓x稱為y的子節點。因為x為y的子節點后,在原x的子樹中,x點到根節點的所有的點的深度會被翻轉過來,所以先access(x),然后在對應的Auxiliary tree中將x旋轉到根節點,,然后將左子樹翻轉(splay中的reverse操作),然后access(y),將y旋轉到對應Auxiliary tree中的根節點,將x連到y就行了。

procedure link(x,y:longint);
begin
  access(x);
  splay(x);
  reverse(son[x,0]);
  access(y);
  splay(y);
  son[y,1]:=x;
  father[x]:=y;
  root[x]:=false;
end;


access操作是LCT的基礎,應該熟練掌握並且理解。

時間復雜度:

  證明access以及其他操作的時間復雜度是均攤log2N的,具體證明參考楊哲的論文《QTREE 解法的一些研究》。

基礎題,bzoj 2002:http://61.187.179.132/JudgeOnline/problem.php?id=2002

/**************************************************************
    Problem: 2002
    User: BLADEVIL
    Language: Pascal
    Result: Accepted
    Time:2372 ms
    Memory:4328 kb
****************************************************************/
 
//By BLADEVIL
var
    n, m                :longint;
    father, size        :array[-1..200010] of longint;
    son                 :array[-1..200010,0..2] of longint;
    root                :array[-1..200010] of boolean;
 
procedure update(x:longint);
begin
    size[x]:=size[son[x,0]]+size[son[x,1]]+1;
end;
     
procedure left_rotate(x:longint);
var
    y                   :longint;
begin
    y:=son[x,1];
    son[x,1]:=son[y,0];
    father[son[x,1]]:=x;
    son[y,0]:=x;
    if x=son[father[x],0] then
        son[father[x],0]:=y else
    if x=son[father[x],1] then
        son[father[x],1]:=y;
    father[y]:=father[x];
    father[x]:=y;
    root[y]:=root[x] xor root[y];
    root[x]:=root[x] xor root[y];
    update(x); update(y);
end;
 
procedure right_rotate(x:longint);
var
    y                   :longint;
begin
    y:=son[x,0];
    son[x,0]:=son[y,1];
    father[son[x,0]]:=x;
    son[y,1]:=x;
    if x=son[father[x],0] then
        son[father[x],0]:=y else
    if x=son[father[x],1] then
        son[father[x],1]:=y;
    father[y]:=father[x];
    father[x]:=y;
    root[y]:=root[y] xor root[x];
    root[x]:=root[y] xor root[x];
    update(x); update(y);
end;
     
procedure splay(x:longint);
begin
    while not root[x] do
        if x=son[father[x],1] then
            left_rotate(father[x]) else
            right_rotate(father[x]);
end;
     
procedure access(x:longint);
var
    y                   :longint;
begin
    splay(x);
    while father[x]<>0 do
    begin
        y:=father[x];
        splay(y);
        root[son[y,1]]:=true;
        root[x]:=false;
        son[y,1]:=x;
        update(y);
        splay(x);
    end;
end;
     
procedure init;
var
    i                   :longint;
begin
    read(n);
    for i:=1 to n do
    begin
        read(father[i]);
        father[i]:=father[i]+i;
        if father[i]>n then father[i]:=n+1;
    end;
    read(m);
end;
 
procedure main;
var
    i                   :longint;
    x, y, z             :longint;
begin
    for i:=1 to n+1 do size[i]:=1;
    fillchar(root,sizeof(root),true);
    for i:=1 to m do
    begin
        read(x);
        if x=1 then
        begin
            read(y); inc(y);
            access(y);
            writeln(size[son[y,0]]);
        end else
        begin
            read(y,z); inc(y);
            splay(y);
            father[son[y,0]]:=father[y];
            root[son[y,0]]:=true;
            son[y,0]:=0; 
            size[y]:=size[son[y,1]]+1;
            father[y]:=y+z;
            if father[y]>n then father[y]:=n+1;
        end;
    end;
end;
     
begin
    init;
    main;
end.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM