搬運:看一道caioj1439
題目描述該類動態樹問題乙個突出點就是動態,假如沒有1、2操作當然可以方便的運用樹鏈剖分演算法水過(詳見第8章 樹鏈剖分)。前一章的伸展樹只支援改變樹的形態,難以對樹的結構進行改變,對於建邊刪邊的操作的需要,我們要運用多棵伸展樹組成新樹,即解決該類動態樹問題的普遍方法,link-cut-tree,俗稱lct。一開始給你一棵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。
它跟樹鏈剖分類似,只不過樹剖用線段樹維護重鏈,而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...