diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 2a4d3da4..215b6791 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -10,6 +10,8 @@
- [Basic Greedy](greedy/basic.md)
- [Dynamic Programming]()
- [Basic DP]()
+- [Data Structure]()
+ - [Link-Cut Tree](data_structure/link_cut_tree.md)
- [Flow]()
- [Sqrt Technique](sqrt/intro.md)
- [Square Root Decomposition](sqrt/sqrt_decomposition.md)
diff --git a/src/data_structure/image/LCT/Auxiliary_Tree_Demo.png b/src/data_structure/image/LCT/Auxiliary_Tree_Demo.png
new file mode 100644
index 00000000..79f11071
Binary files /dev/null and b/src/data_structure/image/LCT/Auxiliary_Tree_Demo.png differ
diff --git a/src/data_structure/image/LCT/access_demo.gif b/src/data_structure/image/LCT/access_demo.gif
new file mode 100644
index 00000000..d9ec8331
Binary files /dev/null and b/src/data_structure/image/LCT/access_demo.gif differ
diff --git a/src/data_structure/image/LCT/access_demo.png b/src/data_structure/image/LCT/access_demo.png
new file mode 100644
index 00000000..a1125565
Binary files /dev/null and b/src/data_structure/image/LCT/access_demo.png differ
diff --git a/src/data_structure/image/LCT/cut_edge.png b/src/data_structure/image/LCT/cut_edge.png
new file mode 100644
index 00000000..e64d683c
Binary files /dev/null and b/src/data_structure/image/LCT/cut_edge.png differ
diff --git a/src/data_structure/link_cut_tree.md b/src/data_structure/link_cut_tree.md
new file mode 100644
index 00000000..b1a168d2
--- /dev/null
+++ b/src/data_structure/link_cut_tree.md
@@ -0,0 +1,851 @@
+# Link-Cut Tree
+
+## 介紹
+
+Link-Cut Tree 是一種樹狀的資料結構,具體來說是一個森林,也就是很多樹的集合,Link-Cut Tree 支援以下操作:
+
+- 在兩個點之間建立一條邊
+- 在兩個點之間斷開一條邊
+- 查詢兩點之間是否存連通
+
+Link-Cut Tree 是以 Splay Tree 為基礎實作,因此尚未了解 Splay Tree 可以先回去參考 Splay Tree,本篇著重於介紹 Link-Cut tree。
+
+以下文章將用 LCT 簡稱 Link-Cut Tree。
+
+## 先備知識
+
+- Splay Tree
+Splay Tree 是一種自平衡的二元搜尋樹,主要通過 Splay 操作讓最近被訪問的節點移動至樹的根部,並且 Splay 操作的時間複雜度為均攤 \\(O(\log n)\\)。
+
+- 輕重鏈剖分
+輕重鏈剖分用來處理樹上的動態查詢,它的精髓在於將樹拆分成很多條鏈,使得樹上的操作可以有好的時間複雜度,LCT 也有使用到類似的技巧,因此建議先理解輕重鏈剖分後再回來看 LCT。
+
+## 名詞定義
+
+### 代表樹
+
+代表樹就是原樹,原樹由一般的邊以及特別的邊 preferred edge 所連接,在進行 LCT 的 ``access()`` 操作時,會把走訪的邊都設為 preferred edge,並且把 preferred edge 所連接到的兒子稱為 preferred child,一條全部由 preferred edge 所構成的 path 則稱為 preferred path。
+
+### Auxiliary Tree
+
+以下使用輔助樹來稱呼 Auxiliary Tree。
+
+要維護 LCT 的操作,我們需要維護輔助樹 (Auxiliary Tree)。輔助樹通常使用 splay tree 來實作,輔助樹的作用是用來維護 preferred edge,同時也會維護輔助樹的訊息,因此輔助樹能維護的訊息,就決定了 LCT 可以維護的訊息。
+每一棵輔助樹都對應到原樹上的一條 preferred edge,而不同的輔助樹之間由 path-parent pointer 連結,這種邊只會由兒子指向父親,而父親不會指向兒子。
+以下是一個原樹與輔助樹的對應關係:
+圖解:左為原樹,原樹的粗邊代表 preferred edge。右為原樹所對應的輔助樹,一般的邊代表 splay node 的左右小孩,而帶有箭號的邊代表 path-parent pointer,用來連接不同的輔助樹。
+在原樹中 ABD 構成一條 preferred path,因此 ABD 在同一個輔助樹中,並且依照原樹的深度進行平衡,除此之外 CG 也構成一條 preferred path,因此 CG 在同一個輔助樹中,最後根據原樹上的邊,將輔助樹彼此用 path-parent pointer 連接。
+
+在基本 LCT 的 Splay Tree 節點會維護以下訊息:
+
+1. 父節點
+2. 左右小孩 (實作時用右小孩代表 preferred edge)
+3. 在 Splay Tree Node 左邊的節點深度比自己小,右邊的節點深度比自己大
+4. 因為 LCT 操作中要維護左右節點深度的性質,所以在某些特定的操作中需要將區間反轉,因此使用懶惰標記,讓翻轉區間能夠有好的時間複雜度
+
+以下是一個簡單的 Splay Tree node:
+
+```cpp
+struct splay_node
+{
+ int child[2], pa;
+ bool rev;
+ splay_node() : pa(0), rev(false), child({0, 0}) {}
+};
+```
+
+- ``child[0]`` 代表左小孩(以下用 lc 表示)、``child[1]`` 代表右小孩 (以下用 rc 表示)
+- ``parent`` 代表父親
+- ``rev`` 代表區間反轉的懶惰標記
+
+接下來就是這棵輔助樹的基本操作。
+
+#### ``splay()`` 以及 ``rotate()``
+
+Splay Tree 的基本操作。
+
+```cpp
+void rotate(int x) // balance splay tree
+{
+ int y = cur.pa, z = node[y].pa, d = (node[y].rc == x);
+ cur.pa = z;
+ if (!isroot(y))
+ node[z].child[node[z].rc == y] = x;
+ node[y].child[d] = cur.child[d ^ 1];
+ node[node[y].child[d]].pa = y;
+ node[y].pa = x, cur.child[d ^ 1] = y;
+ up(y);
+ up(x);
+}
+void splay(int x) // splay node x
+{
+ push_down(x);
+ while (!isroot(x))
+ {
+ int y = cur.pa;
+ if (!isroot(y))
+ {
+ int z = node[y].pa;
+ if ((node[z].lc == y) ^ (node[y].lc == x))
+ rotate(x);
+ else
+ rotate(y);
+ }
+ rotate(x);
+ }
+}
+```
+
+這裡的 ``splay()`` 寫法跟一般的 Splay Tree 不太一樣,LCT 中的 splay 只要到當前這棵輔助樹的樹根即可,因此需要用到 ``isroot()`` 來判斷。
+
+#### ``isroot()``
+
+判斷當前節點是否為整棵樹的根。
+
+```cpp
+bool isroot(int x)
+{
+ return node[cur.pa].lc != x && node[cur.pa].rc != x;
+}
+```
+
+如果父節點的左小孩跟右小孩都不是自己,就代表自己是輔助樹的根,換一種說法就是自己與父節點相連的邊不是 preferred edge。
+
+- ``push_down()``
+遞迴將祖先的懶惰標記往下推。
+
+```cpp
+void push_down(int x)
+{
+ if (!isroot(x))
+ push_down(cur.pa);
+ down(x);
+}
+```
+
+- ``down()``
+真正在做懶惰標記下推的部分。
+
+```cpp
+void down(int x)
+{
+ if (cur.rev)
+ {
+ swap(cur.lc, cur.rc);
+ if (cur.lc)
+ node[cur.lc].rev ^= 1;
+ if (cur.rc)
+ node[cur.rc].rev ^= 1;
+ cur.rev = 0;
+ }
+}
+```
+
+- ``up()``
+``up()`` 可以將子節點的訊息向上更新,可以自行修改成紀錄其他訊息,例如紀錄子樹大小。
+最基本的 LCT 沒有使用到。
+
+### LCT 基本操作
+
+#### ``access()``
+
+``access()`` 是 **LCT 中最重要的函式**,可以把當前節點到 LCT 根結點上面所有的邊變成 preferred edge。
+操作方法:
+
+1. 把當前節點 splay 到目前輔助樹的根
+2. 把當前節點的 preferred child 設定為上一次走到的節點 (將右節點設為上一次走到的點)
+3. 維護節點訊息
+4. 對父節點進行 ``access()``
+重複執行 1~4,直到抵達整棵樹的根結點回傳。
+
+```cpp
+int access(int x)
+{
+ int last = 0;
+ for (; x; last = x, x = cur.pa)
+ {
+ splay(x);
+ cur.rc = last;
+ up(x);
+ }
+ return last;
+}
+```
+
+操作完成後 ``x`` 節點會與根結點存在同一棵輔助樹中。
+
+以下用一個例子來展示 ``access()``:
+
+假設要執行的操作是 ``access(F)``,一開始要先 ``splay(F)``,讓 F 節點變成當前輔助樹的根。``splay(F)`` 後,要把節點的 preferred edge 設定為上次走到的節點,因為 F 是第一個節點,因此不需要動作,接下來繼續往上層更新,對父節點進行 ``access()``,因此接下來要 ``access(C)``。
+先 ``splay(C)``,讓 C 變成輔助樹的根節點。把 C 的 preferred edge 設為 F (這裡為了展示所以將新的 preferred edge 變成粗邊),所以 C 節點拋棄其中一個小孩,這裡展示的是拋棄 B 小孩。最終 ``access(A)``,到達整棵樹的根結點,因此停止操作,最終 F 與根節點 A 的路徑為 preferred path,且 F 與 A 在同一棵輔助樹中。
+
+上圖是 ``access(F)`` 的前後對照圖。
+
+#### ``make_root()``
+
+將當前節點變為整棵樹的 root。操作方法:
+
+1. 先利用 ``access()`` 將當前節點到根節點的邊都變成 preferred edge
+2. ``splay()`` 當前節點,使他變成當前輔助樹的根
+當完成此操作後,會造成此節點到原本的根的路徑全部反轉,為了要維護**左邊的節點深度比自己小,右邊的節點深度比自己大**這個性質,必須將此區間反轉。
+3. 變更當前節點的懶惰標記 ``rev``
+
+```cpp
+void make_root(int x)
+{
+ access(x);
+ splay(x);
+ cur.rev ^= 1;
+}
+```
+
+#### ``link()``
+
+將兩個節點所在的樹合併,兩個節點必須在不同樹。操作方式:
+
+1. 讓其中一個節點 ``x`` 變成根結點
+2. 將 ``x`` 節點的父節點設為另一個節點
+
+```cpp
+void link(int x, int y)
+{
+ make_root(x);
+ cur.pa = y;
+}
+```
+
+#### ``cut()``
+
+斷開兩個節點之間的邊,兩個節點之間必須有邊。操作方式:
+
+1. 將其中一個節點 ``x`` 變成根結點
+2. ``access()`` 另一個節點 ``y``,讓 ``x`` 和 ``y`` 在同一個輔助樹中
+3. ``splay()`` 節點 ``y``,讓他變成輔助樹的根
+此時 ``x`` 節點會成為 ``y`` 節點的左小孩
+4. 斷開 ``x`` , ``y`` 節點之間的連結
+
+```cpp
+void cut(int x, int y)
+{
+ make_root(x);
+ access(y);
+ splay(y);
+ node[y].lc = 0;
+ cur.pa = 0;
+}
+```
+
+另一種 ``cut()`` 操作是針對單一節點,在原樹中斷開該節點與父節點的邊。操作方式:
+
+1. 直接 ``access()`` 該節點,讓他與當前的根在同一個輔助樹中
+2. ``splay()`` 該節點,讓該節點變成新的根結點
+此時父節點位於左小孩的位置
+3. 斷開該節點與父節點的連結
+
+```cpp
+void cut(int x)
+{
+ access(x);
+ splay(x);
+ node[cur.lc].pa = 0;
+ cur.lc = 0;
+}
+```
+
+#### ``find_root()``
+
+尋找此節點所在樹的根結點。操作方式:
+
+1. 首先 ``access()`` 此節點,讓他和根結點在同一個輔助樹裡面
+2. 根據 LCT 的性質,只要找到最左邊的節點代表深度最小,也就是根
+3. 最後記得 ``splay()`` 找到的根
+
+```cpp
+int find_root(int x)
+{
+ int res = access(x);
+ while (node[res].lc)
+ res = node[res].lc;
+ splay(res);
+ return res;
+}
+```
+
+### 各操作時間複雜度
+
+LCT 的操作中,都是基於 ``access()`` 操作而完成的,因此只要能分析出 ``access()`` 複雜度就可以知道各個操作的時間複雜度。
+
+首先 ``access()`` 是由 ``splay()`` 以及迴圈所構成,``splay()`` 操作的時間複雜度是 \\( O(\log N) \\),接下來只要分析出 ``splay()`` 會被執行幾次,也就是 preferred child 的改變數量,就可以知道時間複雜度了。
+
+這裡引入 Heavy-light decomposition 的定義,heavy edge 代表所連接的子節點位於最大的子樹中,light edge 代表所連接的子節點不是最大的子樹,在 LCT 中,每個節點只會有兩個子節點,因此 heavy edge 所連接的子節點具有以下性質:\\( size(子節點) > 1/2 size (父節點) \\),因此我們將邊分為以下四種 heavy-prefered, heavy-unpreferred, light-preferred, light-unpreferred。
+
+在每一次 ``splay()`` 後都會有一個邊被設為 preferred child,如果被設為 prefered child 的邊是 light edge,則這條路徑上最多只會有 \\(\log n\\)條 light edge,因此 ``splay()`` 最多只會被執行 \\(\log n\\)次,接下來討論如果被設為 prefered child 的邊是 heavy edge,因為最多有 \\(n - 1\\)條 heavy edge,因此在最糟的情況下,``splay()`` 會被執行 \\(n - 1\\)次,但在每次 ``access()`` 中,最多只有 \\(\log n\\)條邊從 light-unpreferred edge 變成 light-preferred edge,因此最多只有 \\(\log n\\)條邊從 heavy-preferede edge 變成 heavy-unprefered edge,假設有 \\(m\\) 次操作,花在 heavy edge 上的操作最多為 \\(m(\log n) + (n - 1)\\),在足夠次數的操作時 (\\(m > n - 1\\)),時間複雜度為 \\(O(\log N) \\)。
+
+目前可以得出,最多會有 \\(O(\log N) \\) 次 ``splay()``,因此 ``access()`` 的上界變成了 \\( O(\log^2 N) \\),``splay()`` 執行的次數等價於 preferred child 的改變數量,因此只要能夠讓改變的數量變成均攤 \\(O(1)\\),就可以把 ``access()`` 的時間複雜度變為 \\(O(\log N) \\)。
+
+利用 potential method,假設 \\(s(v)\\) 代表在 \\(v\\) 節點下的子樹大小,所以 potential function 就可以寫成 \\(\Phi = \sum_{v} \log s(v) \\),根據 splay tree 的均攤分析,\\(cost(splay(v)) \leq 3(\log s(root(v)) - \log s(v)) + 1 \\),假設在 ``splay()`` 後,\\(v\\) 是 \\(w\\) 的子節點,可以得到\\(s(v) \leq s(w)\\),最終把 ``splay()`` 的操作與 preferred child 的改變數量加起來,得到\\(3(\log s(root(v)) - \log s(v)) + O(\log n)\\),因此我們得到均攤 \\(O(\log N) \\)的時間複雜度。
+
+需要更清楚的解釋可以參考 ([維基百科](https://en.wikipedia.org/wiki/Link/cut_tree))
+
+有了 ``access()`` 的時間複雜度後,發現剩下的操作都是基於 ``access()`` 以及 ``splay()``,因此各個 LCT 操作的時間複雜度皆是\\( O(\log N) \\)
+
+|操作|時間複雜度|
+|-----|--------|
+|``access()``|\\( O(\log N) \\)|
+|``make_root()``|\\( O(\log N) \\)|
+|``link()``|\\( O(\log N) \\)|
+|``cut()``|\\( O(\log N) \\)|
+|``find_root()``|\\( O(\log N) \\)|
+
+### 最終模板
+
+ Template Code
+
+```cpp
+struct LCT
+{
+ #define cur node[x]
+ #define lc child[0]
+ #define rc child[1]
+ struct splay_node
+ {
+ int child[2], pa;
+ bool rev;
+ splay_node() : pa(0), rev(false), child({0, 0}) {}
+ };
+ std::vector node;
+ LCT(int _size) { node.resize(_size + 1); }
+ bool isroot(int x)
+ {
+ return node[cur.pa].lc != x && node[cur.pa].rc != x;
+ }
+ void down(int x)
+ {
+ if (cur.rev)
+ {
+ swap(cur.lc, cur.rc);
+ if (cur.lc)
+ node[cur.lc].rev ^= 1;
+ if (cur.rc)
+ node[cur.rc].rev ^= 1;
+ cur.rev = 0;
+ }
+ }
+ void push_down(int x)
+ {
+ if (!isroot(x))
+ push_down(cur.pa);
+ down(x);
+ }
+ void up(int x) {}
+ void rotate(int x)
+ {
+ int y = cur.pa, z = node[y].pa, d = (node[y].rc == x);
+ cur.pa = z;
+ if (!isroot(y))
+ node[z].child[node[z].rc == y] = x;
+ node[y].child[d] = cur.child[d ^ 1];
+ node[node[y].child[d]].pa = y;
+ node[y].pa = x, cur.child[d ^ 1] = y;
+ up(y);
+ up(x);
+ }
+ void splay(int x)
+ {
+ push_down(x);
+ while (!isroot(x))
+ {
+ int y = cur.pa;
+ if (!isroot(y))
+ {
+ int z = node[y].pa;
+ if ((node[z].lc == y) ^ (node[y].lc == x))
+ rotate(x);
+ else
+ rotate(y);
+ }
+ rotate(x);
+ }
+ }
+ int access(int x)
+ {
+ int last = 0;
+ for (; x; last = x, x = cur.pa)
+ {
+ splay(x);
+ cur.rc = last;
+ up(x);
+ }
+ return last;
+ }
+ void make_root(int x)
+ {
+ access(x);
+ splay(x);
+ cur.rev ^= 1;
+ }
+ void link(int x, int y)
+ {
+ make_root(x);
+ cur.pa = y;
+ }
+ void cut(int x, int y)
+ {
+ make_root(x);
+ access(y);
+ splay(y);
+ node[y].lc = 0;
+ cur.pa = 0;
+ }
+ void cut(int x)
+ {
+ access(x);
+ splay(x);
+ node[cur.lc].pa = 0;
+ cur.lc = 0;
+ }
+ int find_root(int x)
+ {
+ int res = access(x);
+ while (node[res].lc)
+ res = node[res].lc;
+ splay(res);
+ return res;
+ }
+ #undef cur
+ #undef lc
+ #undef rc
+};
+```
+
+
+
+## LCT 用途
+
+### Template problem
+
+> [DYNACON1](https://www.spoj.com/problems/DYNACON1/)
+> 有一棵無根樹總共有 \\(N\\) 個節點,一開始沒有邊,題目是對於樹進行 \\(Q\\) 筆,操作總共有三種:
+>
+> 1. 連接兩個原本沒有連結的節點
+> 2. 斷開已經有聯結的兩個節點
+> 3. 詢問兩個節點中是否有一條 path
+>
+> - \\( N, Q \leq 10^5 \\)
+
+解題思路:
+可以把題目想像成一個森林,每個節點都是獨立的一棵樹,因此符合 LCT 是一個森林的性質。
+第一個操作的可以用 LCT 的 ``link()`` 來達成。
+第二個的操作可以用 LCT 的 ``cut()`` 來達成。所以只要能判斷兩個節點是否相連就可以回答問題了。
+要判斷兩個節點是否相連其實就等同於兩個節點是否在同一棵樹上,所以我們只要看兩個節點的根是否一樣就可以達成。
+
+時間複雜度分析:
+對於每一個操作都可以在\\( O(\log N) \\)的時間完成,因此總複雜度就是\\( O(Q \log N) \\)
+
+ Solution Code
+
+在 ``cut()`` 之前要先判斷有沒有邊存在,但在這一題他保證邊一定存在,因此可以大膽的給他 ``cut()`` 下去
+
+```cpp
+#include
+using namespace std;
+// LCT template
+int main()
+{
+ ios::sync_with_stdio(false);
+ cin.tie(0);
+ int n, q;
+ cin >> n >> q;
+ LCT lct(n);
+ while (q--)
+ {
+ string str;
+ int u, v;
+ cin >> str >> u >> v;
+ if (str[0] == 'a')
+ lct.link(u, v);
+ else if (str[0] == 'r')
+ lct.cut(u, v);
+ else if (lct.find_root(u) == lct.find_root(v))
+ cout << "YES\n";
+ else
+ cout << "NO\n";
+ }
+ return 0;
+}
+```
+
+
+
+### Maintaining edge weight on LCT
+
+> [SPOJ QTREE](https://www.spoj.com/problems/QTREE/)
+> 給你一棵\\( N \\)個節點的樹,每個邊有邊權\\( w \\),然後會有\\( Q \\)筆操作。操作有兩種:
+>
+> 1. 改動其中一條邊的邊權
+> 2. 詢問節點\\( u \\)到節點\\( v \\)路徑上權重最大的邊
+>
+> - \\( N, Q \leq 10^4 \\)
+
+解題思路:這題可以使用輕重鏈剖分做,但這裡提供 LCT 的做法。(同時也可以比較一下這兩種作法的時間複雜度分別會是多少)
+在 LCT 中我們沒辦法維護邊權,因此我們可以利用**拆邊**的技巧,也就是將將一條邊變成一個點加上兩個邊,而那個點的權重變成原本邊的權重,可以參考下圖:
+
+
+解決邊權的問題後,但在建樹之前要先維護好節點的資訊
+對於每個節點,需要維護:
+
+1. 點權 (程式碼中利用 ``val`` 表示)
+2. Splay Tree 點權的最大值 (程式碼中利用 ``mx`` 表示)
+
+因為需要維護最大值,因此需要改動 ``up()`` 函式,當前節點的最大值就是小孩的最大值跟自己的點權取 max
+
+```cpp
+void up(int x)
+{
+ cur.mx = max(max(node[cur.lc].mx, node[cur.rc].mx), cur.val);
+}
+```
+
+對於題目的操作可以用下面方式達成:
+
+1. 改動其中一條邊的邊權
+直接更改那條邊拆點後的點權即可
+2. 詢問路徑上最大的邊
+與 ``cut()`` 操作相似,先讓 ``u`` 節點變成根,再把 ``v`` 節點旋轉到根,``v`` 節點的值就是答案
+
+時間複雜度分析:
+建樹時需要建立\\( 2N - 2 \\)條邊,建邊時間複雜度為\\( O(\log N) \\),因此建樹時時間複雜度是\\( O(N\log N) \\)
+對於每一個操作都可以在\\( O(\log N) \\)的時間完成,因此\\( Q \\)筆操作時間複雜度就是\\( O(Q \log N) \\)
+總時間複雜度為\\( O(N\log N + Q \log N) \\)
+
+ Solution Code
+
+```cpp
+#include
+using namespace std;
+struct LCT
+{
+ void up(int x)
+ {
+ cur.mx = max(max(node[cur.lc].mx, node[cur.rc].mx), cur.val);
+ }
+ /* other LCT template */
+};
+
+void solve()
+{
+ int n, u, v, w;
+ cin >> n;
+ LCT lct(2 * n);
+ for (int i = 1; i < n; i++) // n + i represent ith edge
+ {
+ cin >> u >> v >> w;
+ lct.node[n + i].val = w;
+ lct.link(u, n + i);
+ lct.link(n + i, v);
+ }
+ string op;
+ while (true)
+ {
+ cin >> op;
+ if (op == "DONE")
+ break;
+ else if (op == "CHANGE")
+ {
+ cin >> u >> w;
+ u += n;
+ lct.access(u);
+ lct.splay(u);
+ lct.node[u].val = w;
+ }
+ else
+ {
+ cin >> u >> v;
+ lct.make_root(u);
+ lct.access(v);
+ lct.splay(v);
+ cout << lct.node[v].mx << '\n';
+ }
+ }
+}
+
+int main()
+{
+ ios::sync_with_stdio(false);
+ cin.tie(0);
+
+ int t;
+ cin >> t;
+ while (t--)
+ {
+ solve();
+ }
+ return 0;
+}
+```
+
+
+
+### ``access()`` 的其他應用
+
+> [CF 117E - Tree or not Tree](https://codeforces.com/problemset/problem/117/E)
+> 給你 \\(N\\) 個車站,一個車站可以連接多個車站,但每次只能往其中一個車站,總共有 \\(N - 1\\) 條單向權重為 \\(d\\) 的鐵路,並且 \\(1\\) 號車站為根節點。
+> 有 \\(M\\) 台火車,每個火車會在 \\(t_i\\) 的時間進入 \\(1\\) 號車站,並且目標車站為 \\(s_i\\)。
+> 在每個時間,你可以對火車站的開關操作,讓火車站前往不同的車站,如果火車無法到達目標車站,則火車會爆炸。
+> 題目要問最晚的爆炸時間,以及最少的操作次數。
+>
+> - \\( N, M \leq 10^5 \\)
+> - \\( 1 \leq d ,s_i, t_i \leq 10^9\\)
+
+雖然沒有明確的 Link 與 Cut 的需要,但仔細思考看看發現這題的火車站所前往的方向,就很像 LCT 中的 preferred edge,而火車前往的路徑就是 LCT 中的 preferred path,因此可以用 LCT 試試看。把每個 LCT 節點想成一個火車站,紀錄當前距離 \\(1\\) 號車站的距離,並且記錄當前最晚被操作的時間點。在 ``access()`` 中每一次的 iteration 都代表著一次操作,可以根據最晚操作的時間點,算出一個區間 \\\((L, R\]\\),代表開關只要在這個時間點中被更改方向,火車就可以前往正確的路徑。把這些區間放進一個 priority_queue 中維護,並且每次挑選最接近當前時間的開關進行操作,如果當前時間超過了當前最早需要的操作,代表火車會爆炸。
+
+ Solution Code
+
+實作細節:
+
+1. 因為節點深度的資訊在這題中不重要,因此不需要維護,這題要維護的是火車站的最後使用到的時間,因此懶惰標記改成紀錄時間。
+2. 在 ``splay()`` 時,需要從當前輔助樹的根節點將懶惰標記下推,否則向上旋轉時,會出現懶惰標記跑掉的錯誤。
+3. 在 ``access()`` 時,需要紀錄區間 \\\((L, R\]\\),並把它記錄起來。
+
+```cpp
+#include
+using namespace std;
+typedef long long ll;
+stack st;
+vector>> G;
+vector> op;
+struct LCT
+{
+ struct splay_node
+ {
+ int child[2], parent;
+ ll time, tag, dist;
+ // Time means the last operation time.
+ // Dist is distance between 1 and current node.
+ splay_node() : parent(0), child(), time(0), tag(0), dist(0) {}
+ };
+ vector node;
+ LCT(int _size) { node.resize(_size + 1); }
+ bool isroot(int x)
+ {
+ return node[cur.parent].lc != x && node[cur.parent].rc != x;
+ }
+ void down(int x)
+ {
+ if (cur.tag)
+ {
+ cur.time = cur.tag;
+ if (cur.lc)
+ node[cur.lc].tag = cur.tag;
+ if (cur.rc)
+ node[cur.rc].tag = cur.tag;
+ cur.tag = 0;
+ }
+ }
+ void rotate(int x)
+ {
+ int y = cur.parent, z = node[y].parent, d = (node[y].rc == x);
+ cur.parent = z;
+ if (!isroot(y))
+ node[z].child[node[z].rc == y] = x;
+ node[y].child[d] = cur.child[d ^ 1];
+ node[node[y].child[d]].parent = y;
+ node[y].parent = x, cur.child[d ^ 1] = y;
+ }
+ void splay(int x)
+ { /* Very important: Need to push down from root */
+ int u = x;
+ st.push(u);
+ while (!isroot(u))
+ st.push(u = node[u].parent);
+ while (st.size())
+ {
+ u = st.top();
+ st.pop();
+ down(u);
+ }
+
+ while (!isroot(x))
+ {
+ int y = cur.parent;
+ if (!isroot(y))
+ {
+ int z = node[y].parent;
+ if ((node[z].lc == y) ^ (node[y].lc == x))
+ rotate(x);
+ else
+ rotate(y);
+ }
+ rotate(x);
+ }
+ }
+ int access(int x, int t)
+ {
+ int last = 0, ori = x;
+ for (; x; last = x, x = cur.parent)
+ {
+ splay(x);
+ if (last)
+ {
+ cur.rc = last;
+ a.emplace_back(cur.time + cur.dist, t + cur.dist);
+ }
+ }
+ splay(ori);
+ node[node[ori].lc].tag = t;
+ return last;
+ }
+ void dfs(int x, int pa = 0)
+ { /* Use dfs to build tree. */
+ cur.parent = pa;
+ cur.time = -cur.dist;
+ for (auto [v, w] : G[x])
+ {
+ if (v == pa)
+ continue;
+ cur.rc = v;
+ node[v].dist = cur.dist + w;
+ dfs(v, x);
+ }
+ }
+};
+
+int main()
+{
+ ios::sync_with_stdio(false);
+ cin.tie(0);
+
+ int n, m;
+ cin >> n >> m;
+ LCT lct(n);
+ G.resize(n + 1);
+ for (int i = 1; i < n; i++)
+ {
+ int u, v, w;
+ cin >> u >> v >> w;
+ G[u].emplace_back(v, w);
+ G[v].emplace_back(u, w);
+ }
+ lct.dfs(1);
+
+ for (int i = 1; i <= m; ++i)
+ {
+ int s, t;
+ cin >> s >> t;
+ lct.access(s, t);
+ }
+ sort(op.begin(), op.end());
+ priority_queue, greater> q;
+ ll last = 0;
+ int idx = 0;
+ while (!q.empty() || idx != op.size())
+ {
+ if (q.empty())
+ last = op[idx].first;
+ while (idx != op.size() && op[idx].first == last)
+ q.push(op[idx++].second);
+ if (last == q.top())
+ {
+ int ans = 0;
+ for (auto [start, end] : op)
+ if (end < last)
+ ans++;
+ cout << last << ' ' << ans << '\n';
+ return 0;
+ }
+ q.pop(), last++;
+ }
+ cout << -1 << ' ' << op.size() << '\n';
+
+ return 0;
+}
+```
+
+時間複雜度分析:
+時間複雜度被操作所執行的次數所決定,由於 ``access()`` 操作有均攤 \\(O (\log N)\\) 的時間複雜度,因此操作最多執行的次數為 \\(O (M \log N)\\)。最終用 priority_queue 維護這些操作,因此最終的時間複雜度為 \\(O (N \log N + M \log^2 N)\\)
+
+
+
+### 總結
+
+- LCT 可以判斷連通性
+- LCT 可以做路徑維護,因此可以維護路徑資訊
+- LCT 可以動態切邊,因此當看到 **cut** 關鍵字的時候,或許可以想想看 LCT 可不可以做,因為大部分輕重鏈剖分可以做的題目,LCT 也都可以做
+- LCT 的 ``access()`` 操作可以在 \\(O (\log N)\\) 的時間複雜度完成,因此可以利用這個性質去修改 ``access()``,讓操作有更好的時間複雜度
+
+## Exercise
+
+模板題:
+[DYNALCA - Dynamic LCA](https://www.spoj.com/problems/DYNALCA/)
+ Solution Code
+
+詢問 lca 時,先 ``access()`` 其中一個節點,再 ``access()`` 另一個節點,即可找到 lca
+
+```cpp
+#include
+using namespace std;
+// LCT template
+int main()
+{
+ ios::sync_with_stdio(false);
+ cin.tie(0);
+
+ int n, m, u, v;
+ cin >> n >> m;
+ LCT lct(n);
+ while (m--)
+ {
+ string query;
+ cin >> query;
+ if (query == "link")
+ {
+ cin >> u >> v;
+ lct.link(u, v);
+ }
+ else if (query == "cut")
+ {
+ cin >> u;
+ lct.cut(u);
+ }
+ else if (query == "lca")
+ {
+ cin >> u >> v;
+ lct.access(u);
+ cout << lct.access(v) << '\n';
+ }
+ }
+
+ return 0;
+}
+```
+
+
+
+SPOJ-QTREE 系列題目:
+
+- [QTREE](https://www.spoj.com/problems/QTREE/)
+- [QTREE2](https://www.spoj.com/problems/QTREE2/)
+- [QTREE3](https://www.spoj.com/problems/QTREE3/)
+- [QTREE4](https://www.spoj.com/problems/QTREE4/)
+- [QTREE5](https://www.spoj.com/problems/QTREE5/)
+- [QTREE6](https://www.spoj.com/problems/QTREE6/)
+- [QTREE7](https://www.spoj.com/problems/QTREE7/)
+
+QTREE 系列題目可以參考這篇文章:[【Qtree】Query on a tree 系列 LCT 解法](https://blog.csdn.net/thy_asdf/article/details/50768620)
+
+CF 相關題目:
+
+- [CF 13E - Holes](https://codeforces.com/contest/13/problem/E)
+- [CF 117E - Tree or not Tree](https://codeforces.com/problemset/problem/117/E)
+- [CF 342E - Xenia and Tree](https://codeforces.com/problemset/problem/342/E)
+- [CF 1172E - Nauuo and ODT](https://codeforces.com/contest/1172/problem/E)
+- [CF 1344E - Train Tracks](https://codeforces.com/problemset/problem/1344/E)
+
+## 參考資料
+
+- [Link/cut tree - Wikipedia](https://en.wikipedia.org/wiki/Link/cut_tree)
+- [Link Cut Tree - OI Wiki](https://oi-wiki.org/ds/lct/)
+- [日月卦長的模板庫: [ link-cut tree ] 動態樹教學+模板]()
+- [Splay tree tutorial - Codeforces](https://codeforces.com/blog/entry/79524)
+- [Link-cut tree tutorial - Codeforces](https://codeforces.com/blog/entry/80383)
+- [【Qtree】Query on a tree 系列 LCT 解法](https://blog.csdn.net/thy_asdf/article/details/50768620)