關於樹論 動態樹問題(LCT)

2022-04-30 06:18:08 字數 3258 閱讀 5449

搬運:看一道caioj1439

題目描述 

一開始給你一棵n個點n-1條邊的樹,每個點有乙個權值wi。 

三種操作: 

op=1 u v :在點u和點v之間建一條邊。 

op=2 u v:摧毀點u到點v之間的邊。 

op=3 w u v:將點u和點v之間路徑上的點(包括u,v),權值增加w。 

op=4 u v:詢問點u到點v之間路徑上的點(包括u,v),權值最大值。 

當操作違法時(詢問一中u,v已經相連,二三四中u,v不聯通,一二操作u==v)不進行操作並輸出-1。 

輸入 從檔案weight.in中讀取輸入。 

第1行為1個正整數n,表示點的個數。 

第2~n行為開始樹所有的邊,每行兩個正整數u,v,代表u和v之間有一條邊。 

第n+1行有n個正整數,表示一開始點的權值。 

下一行為乙個正整數m代表下來有m個操作。 

以下m行,一行表示乙個操作。 

行首先輸入乙個正整數op。 

當op=1,2,4時,輸入兩個正整數u,v 

當op=3時,輸入三個正整數w,u,v 

操作如題意 

輸出 輸出到檔案weight.out。 

對每個4操作,輸出點u到點v之間路徑上的點(包括u,v),權值最大值。同時對於違法情況輸出-1。

該類動態樹問題乙個突出點就是動態,假如沒有1、2操作當然可以方便的運用樹鏈剖分演算法水過(詳見第8章 樹鏈剖分)。前一章的伸展樹只支援改變樹的形態,難以對樹的結構進行改變,對於建邊刪邊的操作的需要,我們要運用多棵伸展樹組成新樹,即解決該類動態樹問題的普遍方法,link-cut-tree,俗稱lct。 

它跟樹鏈剖分類似,只不過樹剖用線段樹維護重鏈,而lct用伸展樹(是不是很高大上),在兩棵伸展樹之間,如果它們屬於同乙個lct,那麼將有一條虛邊,連線著它們,在不影響伸展樹的正常操作前提上,保持應有的連繫。 

大家可以感性的認識….可以假設一開始問題給出的樹邊都是虛邊,我們人為的在上面畫重鏈,每條重鏈用一棵伸展樹維護他(就跟線段樹乙個道理嘛,目標是減少暴力列舉的時間,只不過伸展樹更加快捷靈活),關鍵的,如果沒有連邊刪邊操作,同伸展樹一樣,整棵樹的結構是不變的。 

當然啦,題目也可能給出很多棵樹,我們可以臆想一下,這些樹都屬於0節點的子樹,只不過他們連的邊被「操作刪除」了,這樣也是合理的。同樣道理,當我們在解決動態樹問題的過程中,有時也會出現這棵樹被分成多份。也就是說,link-cut-tree本質上這個圖可以是乙個森林。

講講操作吧。 

最重要的access(x):令x到當前所處的樹的根這條路徑成為偏愛路徑(相當於樹剖的重鏈),然後用splay維護,這是與樹剖最大的不同,這樣的靈活性也符合動態樹。 

make_root(x):令x成為當前樹的根,但是!!不是在當前重鏈中伸展樹的根,也不是整個圖中所有點的根,而是,x當前所處的樹的根!由於lct的link和cut操作,注定了整個圖可能出現多棵樹,樹與樹之間如果不新增邊,都是乙個獨立的動態樹。 

link(x,y):讓x成為根,然後連一條虛邊到y就ok了。 

cut(x,y):先將x設為根(假設現在是點1)假設y是點6,那我們將1~6的路徑設為偏愛路徑(放在一棵伸展樹里)將6旋轉到伸展樹的根,可以發現,點1肯定在伸展樹的最左端,讓y斷開與左端的連線就行了。 

findroot(x):同理,真正在樹中的根肯定在樹的最左邊,所以說找根其實很簡單。 

ps:所以在make_root後要讓整棵伸展樹翻轉,比如說將6變為根,1,4都在它左邊,這樣就不科學了。

#include#include

#include

#include

#include

using

namespace

std;

struct

node

tr[310000

];void add(int

x)void update(int

x)void reverse(int

x)void rotate(int x,int

w)int tmp[310000

];void splay(int x,int

rt)

tmp[++s]=i;

while(s!=0

)

while(tr[x].f!=rt&&(tr[tr[x].f].son[0]==x||tr[tr[x].f].son[1]==x))//

還有虛邊啊!

else

else

if(tr[f].son[1]==x&&tr[ff].son[0]==f)

else

if(tr[f].son[0]==x&&tr[ff].son[1]==f)

else

if(tr[f].son[1]==x&&tr[ff].son[1]==f)}}

}int n,w[310000

];void

make_tree()

}void access(int x)//

訪問x

//還記得樹剖的重兒子嗎?這是令點x到整棵動態樹的根這條路徑變成偏愛路徑(相當於樹剖的重鏈),這一條路徑就是一棵伸展樹。

}void makeroot(int x)//

讓x成為當前樹的根

void link(int x,int

y)void cut(int x,int

y)int find_root(int x)//

訪問完x後,x所屬的伸展樹的最左端的點就是所在樹真正的根,因為伸展樹實際意義上就是一條鏈啊!!

void increase(int x,int y,int w)//

令x,y處於一棵伸展樹,y為根,由於是鏈,直接更新y的ad就行了

int findmax(int x,int y)//

同理,這也是一樣的

struct

edge

e[310000

];int

main()

else

if(op==2

)

else

if(op==3

)

else

}printf("\n

");}

return0;

}

動態樹LCT 模板

題目描述 輸入 第一行兩個整數n和m 接下來一行中n個整數表示初始點權 接下來m行每行乙個操作如上表所示。輸出 對於每乙個連線操作,若p和q不連通,輸出yes,並新增這條邊 否則輸出no 對於每乙個刪除操作,若p和q間有邊,輸出yes,並刪除這條邊,否則輸出no 對於每乙個查詢最大及查詢和,若p和q...

詳解動態樹 LCT)

lct的功能 題意 乙個圖,有n個點,一開始圖中沒有邊。三種操作 connect u v 在點u和點v之間建一條邊。保證所有connect操作不會重複建邊。destroy u v 摧毀點u到點v之間的邊。保證所有destroy操作將摧毀的是一條存在的邊。query u v 詢問點u和點v是否聯通,是...

動態樹 LCT 錯誤總結

彙總犯過的一大堆神奇錯誤。例 node findroot node u return u 解決方法 寫完後搜尋所有 ch,檢查是否之前已pushdown 解決方法 使u uu結點懶標記意義表示u uu的兒子結點需更新,而不是u uu需要更新。懶標記下傳後未清空 例 void pushdown 例 n...