Euler Tour Tree (Cây Du lịch Euler, Cây Chu trình Euler, sau đây gọi tắt là ETT) là một cấu trúc dữ liệu có thể giải quyết các bài toán cây động (dynamic tree). ETT chuyển các thao tác của cây động thành các thao tác trên đoạn của dãy thứ tự DFS của nó, sau đó dùng các cấu trúc dữ liệu khác để duy trì các thao tác trên đoạn của dãy, từ đó duy trì các thao tác của cây động. Ví dụ, ETT chuyển thao tác thêm cạnh của cây động thành nhiều thao tác tách dãy và hợp dãy, nếu có thể duy trì các thao tác tách dãy và hợp dãy, có thể duy trì thao tác thêm cạnh của cây động.
LCT (Link/Cut Tree) cũng là một cấu trúc dữ liệu có thể giải quyết các bài toán cây động, so với ETT thì LCT phổ biến hơn. LCT thực sự thích hợp hơn để duy trì thông tin trên đường đi (path), trong khi ETT thích hợp hơn để duy trì thông tin cây con (subtree). Ví dụ, ETT có thể duy trì giá trị nhỏ nhất trên cây con mà LCT không thể.
ETT có thể sử dụng bất kỳ cấu trúc dữ liệu nào để duy trì, chỉ cần cấu trúc dữ liệu đó hỗ trợ các thao tác trên đoạn dãy tương ứng, và thỏa mãn yêu cầu về độ phức tạp. Thông thường sẽ sử dụng các cây tìm kiếm nhị phân cân bằng như Splay, Treap để duy trì các thao tác trên đoạn, mà các cấu trúc dữ liệu này duy trì các thao tác trên đoạn với độ phức tạp đều là \(O(\log n)\), từ đó có thể duy trì các thao tác cây động trong thời gian \(O(\log n)\). Nếu sử dụng cây tìm kiếm cân bằng đa ngôi (multiway balanced search tree) ví dụ như B-tree để duy trì các thao tác trên đoạn, cũng có thể đạt được độ phức tạp tốt hơn.
Thực ra ETT có thể được hiểu là một tư tưởng, đó là thông qua việc duy trì một dãy nào đó tương ứng một-một với cây gốc, từ đó đạt được mục đích duy trì cây gốc. Bài viết này chỉ giới thiệu một số cách triển khai và ứng dụng khả thi của tư tưởng này.
Biểu diễn Chu trình Euler của cây
Nếu coi một cạnh của cây là hai cạnh có hướng, thì có thể biểu diễn cây thành một chu trình Euler của đồ thị có hướng, gọi là Biểu diễn Chu trình Euler của cây (Euler tour representation, ETR).
Dãy cần duy trì sau này thực chất là một biến thể của ETR, coi các nút trong cây như là vòng lặp tự thân cũng được thêm vào ETR, nhưng do tác giả trong bài báo gốc không đặt tên mới cho nó, nên vẫn gọi nó là ETR.
Có thể thu được Biểu diễn Chu trình Euler của cây \(T\) thông qua thuật toán sau:
\[
\begin{array}{ll}
1 & \textbf{Input. } \text{Một cây có gốc }T\\
2 & \textbf{Output. } \text{Dãy DFS của cây có gốc }T\\
3 & \operatorname{ET}(u)\\
4 & \qquad \text{thăm đỉnh }u\\
5 & \qquad \text{cho tất cả con } v \text{ của } u\\
6 & \qquad \qquad \text{thăm cạnh có hướng } u \to v\\
7 & \qquad \qquad \operatorname{ET}(v)\\
8 & \qquad \qquad \text{thăm cạnh có hướng } v \to u\\
\end{array}
\]
Biểu diễn Chu trình Euler \(\operatorname{ETR}(T)\) của cây \(T\) ban đầu là rỗng. Trong quá trình DFS, mỗi lần thăm một nút hoặc một cạnh có hướng thì thêm nó vào cuối \(\operatorname{ETR}(T)\), như vậy có thể thu được \(\operatorname{ETR}(T)\).
Nếu \(T\) chứa \(n\) nút, thì chứa \(2n - 2\) cạnh có hướng, mà trong quá trình DFS, mỗi nút và mỗi cạnh có hướng đều được thăm một lần, nên độ dài của \(\operatorname{ETR}(T)\) là \(3n - 2\).
Coi nút \(u\) như một vòng lặp tự thân, khi đó \(\operatorname{ETR}(T)\) có thể coi là một chu trình Euler trong đồ thị có hướng. Có thể cắt tại một điểm nào đó trong chu trình Euler, biến nó thành một chuỗi các cạnh nối đầu cuối với nhau; cũng có thể dán lại chỗ bị cắt để biến chuỗi này trở lại thành chu trình Euler; cũng có thể thông qua việc thêm một số cạnh để ghép hai chuỗi như vậy thành một chu trình Euler mới.
Trong phần sau, nếu không nói rõ, dãy được duy trì mặc định là biểu diễn chu trình Euler của cây.
Các thao tác cơ bản của ETT
3 thao tác sau được coi là các thao tác cơ bản của ETT, đều có thể được chuyển đổi thành số lần thao tác trên dãy là hằng số, vì vậy độ phức tạp của 3 thao tác này cùng bậc với thao tác trên dãy.
Ở đây chỉ đưa ra một cách triển khai khả thi, chỉ cần có thể ghép dãy tương ứng với thao tác sau khi sửa đổi bằng số lần thao tác trên dãy là hằng số là được.
MakeRoot(u)
Tức là thao tác đổi gốc. Thao tác đổi gốc trong ETT được chuyển đổi thành 1 thao tác tách dãy và 1 thao tác hợp dãy, cũng có thể hiểu là 1 thao tác dịch chuyển đoạn.
Ký hiệu cây chứa nút \(u\) là \(T\), gốc hiện tại là \(r\), bây giờ muốn đổi gốc thành \(u\). Dãy tương ứng với cây \(T\) là \(L\), tách \(L\) tại \((u, u)\) thành dãy \(L^1\) và \(L^2\), phần trước chứa các phần tử trước \((u, u)\) trong \(L\) và \((u, u)\), phần sau chứa các phần tử còn lại. Dãy thu được bằng cách hợp nhất lần lượt \(L^2\) và \(L^1\) là dãy tương ứng với cây sau khi đổi gốc.
Ở đây có thể hiểu là thao tác xoay chu trình Euler. Chu trình Euler là một vòng tròn, việc xoay không làm thay đổi cấu trúc của chu trình Euler, tức là không làm thay đổi cấu trúc cây, chỉ là đưa nút \(u\) xoay đến vị trí gốc.
Insert(u, v)
Tức là thao tác thêm cạnh. Thao tác thêm cạnh trong ETT được chuyển đổi thành 2 thao tác tách dãy và 5 thao tác hợp dãy.
Ký hiệu cây chứa nút \(u\) là \(T_1\), cây chứa nút \(v\) là \(T_2\), sau khi thêm cạnh hai cây hợp nhất thành một cây \(T\). Dãy tương ứng với cây \(T_1\) là \(L_1\), dãy tương ứng với cây \(T_2\) là \(L_2\).
Tách \(L_1\) tại \((u, u)\) thành dãy \(L_1^1\) và \(L_1^2\), phần trước chứa các phần tử trước \((u, u)\) trong \(L_1\) và \((u, u)\), phần sau chứa các phần tử còn lại. Tương tự, tách \(L_2\) tại \((v, v)\) thành dãy \(L_2^1\) và \(L_2^2\). Dãy tương ứng với cây \(T\) là \(L\) thu được bằng cách hợp nhất lần lượt \(L_1^2, L_1^1, [(u, v)], L_2^2, L_2^1, [(v, u)]\).
Ở đây có thể hiểu là hai thao tác đổi gốc, sau đó ngắt hai chu trình Euler tại vị trí gốc hiện tại, rồi dùng hai cạnh có hướng mới để ghép hai chu trình Euler thành một chu trình Euler mới.
Delete(u, v)
Tức là thao tác xóa cạnh. Thao tác xóa cạnh trong ETT được chuyển đổi thành 4 thao tác tách dãy và 1 thao tác hợp dãy.
Ký hiệu cây chứa cạnh \((u, v)\) và cạnh \((v, u)\) là \(T\), dãy tương ứng là \(L\). Sau khi xóa cạnh, \(T\) được chia thành hai cây.
Tách \(L\) thành \(L_1, [(u, v)], L_2, [(v, u)], L_3\). Dãy tương ứng với hai cây tạo thành sau khi xóa cạnh lần lượt là \(L_2\) và \(L_1, L_3\). Chú ý, trong dãy \(L\), \([(u, v)]\) có thể xuất hiện sau \([(v, u)]\), khi đó có thể đổi giá trị \(u\) và \(v\) rồi mới thao tác.
Ở đây có thể hiểu là cắt một chu trình Euler tại hai cạnh có hướng để tạo thành hai chuỗi, sau đó hai chuỗi tự nối đầu cuối để tạo thành hai chu trình Euler mới.
Triển khai
Dưới đây lấy Treap không xoay (non-splay Treap) làm ví dụ để giới thiệu cách triển khai ETT, đòi hỏi người đọc cần biết trước về nội dung liên quan đến thao tác trên đoạn của cây tìm kiếm nhị phân không xoay.
Split và Merge đều là các thao tác cơ bản của Treap không xoay, ở đây không nhắc lại.
SplitUp2(u)
Giả sử dãy mà \(u\) thuộc về là \(L\), tách \(L\) tại \(u\) thành dãy \(L^1\) và \(L^2\), phần trước chứa các phần tử trước \((u, u)\) trong \(L\) và \((u, u)\), phần sau chứa các phần tử còn lại.
Nếu mỗi nút trong Treap còn duy trì cha của nó, thì có thể thực hiện tính toán vị trí của phần tử tương ứng với nút Treap trong dãy trong thời gian \(O(\log n)\), sau đó dựa vào vị trí để Split có thể thực hiện chức năng trên.
Cũng có thể tách từ dưới lên để thực hiện chức năng trên, cách này so với phương pháp trên sẽ hiệu quả hơn. Cụ thể là, trong quá trình nhảy từ nút \(u\) lên gốc, dựa vào tính chất của cây tìm kiếm nhị phân có thể xác định mỗi nút trong \(L\) nằm trước hay sau \(u\), dựa vào điều này có thể tính toán vị trí của \(u\) trong dãy, cũng có thể xác định mỗi nút thuộc về cây nào trong các cây sau khi tách.
/* * Bottom up split treap p into 2 treaps a and b. * - a: a treap containing nodes with position less than or equal to p. * - b: a treap containing nodes with postion greater than p. * * In the other word, split sequence containning p into two sequences, the first * one contains elements before p and element p, the second one contains * elements after p. */staticstd::pair<Node*,Node*>SplitUp2(Node*p){Node*a=nullptr,*b=nullptr;b=p->right_;if(b)b->parent_=nullptr;p->right_=nullptr;boolis_p_left_child_of_parent=false;boolis_from_left_child=false;while(p){Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}if(!is_from_left_child){a=Merge(p,a);}else{b=Merge(b,p);}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;}return{a,b};}
SplitUp3(u)
Giả sử dãy mà \(u\) thuộc về là \(L\), tách \(L\) tại \(u\) thành dãy \(L^1, u\) và \(L^2\), phần trước chứa các phần tử trước \(u\) trong \(L\), phần sau chứa các phần tử còn lại.
Có thể thực hiện chức năng trên bằng cách sửa đổi một chút dựa trên SplitUp2.
Hai điểm \(u\) và \(v\) liên thông khi và chỉ khi hai điểm thuộc cùng một cây \(T\), tức là \((u, u)\) và \((v, v)\) thuộc \(\operatorname{ETR}(T)\), điều này có thể được phán đoán bằng cách kiểm tra xem nút Treap tương ứng với nút \(u\) và nút \(v\) có nằm trong cùng một Treap (có cùng nút gốc) hay không.
#include<cassert>#include<cstdint>#include<functional>#include<iostream>#include<map>#include<random>#include<sstream>usingi64=int64_t;usingu64=uint64_t;voidsolve_case(intCase);intmain(intargc,char*argv[]){std::ios::sync_with_stdio(false),std::cin.tie(nullptr);intT=1;// std::cin >> T;for(intt=1;t<=T;++t){solve_case(t);}return0;}/** * Dynamic Forest Maintained With Euler Tour Tree. * * As said in reference, link and cut operation of dynamic trees can be * transformed into sequence split and sequence merge operation, which can be * easily maintained using balanced search trees like Treap. * * @reference: Dynamic trees as search trees via euler tours, applied to the * network simplex algorithm by Robert E. Tarjan. * https://link.springer.com/article/10.1007/BF02614369 */classDynamicForest{private:staticstd::mt19937rng_;structNode{intsize_;intpriority_;Node*left_;Node*right_;Node*parent_;intfrom_;intto_;Node(intfrom,intto):size_(1),priority_(rng_()),left_(nullptr),right_(nullptr),parent_(nullptr),from_(from),to_(to){}voidMaintain(){size_=1;if(left_){size_+=left_->size_;left_->parent_=this;}if(right_){size_+=right_->size_;right_->parent_=this;}}};staticintGetSize(Node*p){returnp==nullptr?0:p->size_;}staticNode*FindRoot(Node*p){if(!p)returnnullptr;while(p->parent_!=nullptr)p=p->parent_;returnp;}staticstd::stringto_string(Node*p){std::stringstreamss;ss<<"Node [\n";std::function<void(Node*)>dfs=[&](Node*p){if(!p)return;dfs(p->left_);ss<<"("<<p->from_<<","<<p->to_<<"),";dfs(p->right_);};dfs(p);ss<<"]\n\n";returnss.str();}Node*AllocateNode(intu,intv){Node*p=newNode(u,v);returnp;}voidFreeNode(Node*&p){if(p){deletep;p=nullptr;}}/* * Dynamic Sequence Maintained using Treap. */classTreap{public:/** * Merge two treap a and b into a single treap, with keys in a less than * keys in b. * * In the other word, concating sequence a and sequence b. */staticNode*Merge(Node*a,Node*b){if(a==nullptr)returnb;if(b==nullptr)returna;if(a->priority_<b->priority_){a->right_=Merge(a->right_,b);a->Maintain();returna;}else{b->left_=Merge(a,b->left_);b->Maintain();returnb;}}/** * Get the number of nodes with keys less than or equal to the key of p. * * In the other word, the the 1-based index of p inside the sequencec * containing p. */staticintGetPosition(Node*p){assert(p!=nullptr);intposition=GetSize(p->left_)+1;while(p){if(p->parent_&&p==p->parent_->right_)position+=GetSize(p->parent_->left_)+1;p=p->parent_;}returnposition;}/** * Split sequence containning p into two sequences, the first one contains * the first k elements, the second one contains the remaining elements. */staticstd::pair<Node*,Node*>Split(Node*p,intk){if(!p)return{nullptr,nullptr};std::pair<Node*,Node*>result;if(GetSize(p->left_)<k){autoright_result=Split(p->right_,k-GetSize(p->left_)-1);p->right_=right_result.first;result.first=p;result.second=right_result.second;}else{autoleft_result=Split(p->left_,k);p->left_=left_result.second;result.first=left_result.first;result.second=p;}p->Maintain();if(result.first)result.first->parent_=nullptr;if(result.second)result.second->parent_=nullptr;returnresult;}/* * Bottom up split treap p into 2 treaps a and b. * - a: a treap containing nodes with position less than or equal to p. * - b: a treap containing nodes with postion greater than p. * * In the other word, split sequence containning p into two sequences, the * first one contains elements before p and element p, the second one * contains elements after p. */staticstd::pair<Node*,Node*>SplitUp2(Node*p){assert(p!=nullptr);Node*a=nullptr,*b=nullptr;b=p->right_;if(b)b->parent_=nullptr;p->right_=nullptr;boolis_p_left_child_of_parent=false;boolis_from_left_child=false;while(p){Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}if(!is_from_left_child){a=Merge(p,a);}else{b=Merge(b,p);}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;}return{a,b};}/* * Bottom up split treap p into 3 treaps a, b and c. * - a: a treap containing nodes with key less than p. * - b: a treap containing nodes with key greater than p. * - c: a treap containing nodes with key equal p. * * In the other word, split sequence containning p into three sequences, the * first one contains elements before p, the second one contains element p, * the third one contains elements after p. */staticstd::tuple<Node*,Node*,Node*>SplitUp3(Node*p){assert(p!=nullptr);Node*a=p->left_;if(a)a->parent_=nullptr;p->left_=nullptr;Node*b=p->right_;if(b)b->parent_=nullptr;p->right_=nullptr;Node*c=p;boolis_p_left_child_of_parent=false;boolis_from_left_child=false;Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;while(p){Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}if(!is_from_left_child){a=Merge(p,a);}else{b=Merge(b,p);}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;}return{a,c,b};}};public:DynamicForest(intn):n_(n),vertices_(n_),tree_edges_(n_){assert(n_>0);for(inti=0;i<n_;++i)vertices_[i]=AllocateNode(i,i);}~DynamicForest(){for(inti=0;i<n_;++i){for(autop:tree_edges_[i]){FreeNode(p.second);}}for(inti=0;i<n_;++i){FreeNode(vertices_[i]);}}voidInsert(intu,intv){assert(!tree_edges_[u].count(v));assert(!tree_edges_[v].count(u));Node*vertex_u=vertices_[u];Node*vertex_v=vertices_[v];Node*edge_uv=AllocateNode(u,v);Node*edge_vu=AllocateNode(v,u);tree_edges_[u][v]=edge_uv;tree_edges_[v][u]=edge_vu;intposition_u=Treap::GetPosition(vertex_u);intposition_v=Treap::GetPosition(vertex_v);autop1=Treap::SplitUp2(vertex_u);autop2=Treap::SplitUp2(vertex_v);autoL11=p1.first,L12=p1.second;autoL21=p2.first,L22=p2.second;assert(GetSize(L11)==position_u);assert(GetSize(L21)==position_v);Node*result=nullptr;result=Treap::Merge(result,L12);result=Treap::Merge(result,L11);result=Treap::Merge(result,edge_uv);result=Treap::Merge(result,L22);result=Treap::Merge(result,L21);result=Treap::Merge(result,edge_vu);}voidDelete(intu,intv){assert(tree_edges_[u].count(v));assert(tree_edges_[v].count(u));Node*edge_uv=tree_edges_[u][v];Node*edge_vu=tree_edges_[v][u];tree_edges_[u].erase(v);tree_edges_[v].erase(u);intposition_uv=Treap::GetPosition(edge_uv);intposition_vu=Treap::GetPosition(edge_vu);if(position_uv>position_vu){std::swap(edge_uv,edge_vu);std::swap(position_uv,position_vu);}autop1=Treap::SplitUp3(edge_uv);autoL1=std::get<0>(p1),uv=std::get<1>(p1);assert(GetSize(L1)==position_uv-1);assert(GetSize(uv)==1);autop2=Treap::SplitUp3(edge_vu);autoL2=std::get<0>(p2),vu=std::get<1>(p2),L3=std::get<2>(p2);assert(GetSize(L2)==position_vu-position_uv-1);assert(GetSize(vu)==1);L1=Treap::Merge(L1,L3);FreeNode(edge_uv);FreeNode(edge_vu);}boolIsConnected(intu,intv){Node*vertex_u=vertices_[u];Node*vertex_v=vertices_[v];returnFindRoot(vertex_u)==FindRoot(vertex_v);}std::stringto_string()const{std::stringstreamss;ss<<"DynamicForest [\n";std::function<void(Node*)>dfs=[&](Node*p){if(!p)return;dfs(p->left_);ss<<"("<<p->from_<<","<<p->to_<<"),";dfs(p->right_);};for(inti=0;i<n_;++i){if(vertices_[i]->parent_==nullptr){ss<<" Component [";dfs(vertices_[i]);ss<<"]\n";}}for(inti=0;i<n_;++i){for(autop:tree_edges_[i]){if(p.second->parent_==nullptr){ss<<" Component [";dfs(p.second);ss<<"]\n";}}}ss<<"]\n\n";returnss.str();}private:intn_;std::vector<Node*>vertices_;std::vector<std::map<int,Node*>>tree_edges_;};std::mt19937DynamicForest::rng_(std::random_device{}());voidsolve_case(intCase){intn,m;std::cin>>n>>m;DynamicForestt(n+1);std::stringop;intu,v;for(inti=1;i<=m;++i){std::cin>>op;std::cin>>u>>v;if(op[0]=='Q'){std::cout<<(t.IsConnected(u,v)?"Yes":"No")<<"\n";}elseif(op[0]=='C'){t.Insert(u,v);}elseif(op[0]=='D'){t.Delete(u,v);}}}
Duy trì thông tin cây con
Dưới đây lấy số lượng nút cây con làm ví dụ để giải thích.
Đối với mỗi phần tử trong \(\operatorname{ETR}(T)\), nếu phần tử đó tương ứng với một nút trong cây, thì gán trọng số của nó là \(1\); nếu phần tử đó tương ứng với một cạnh trong cây, thì gán trọng số của nó là \(0\). Số lượng nút của cây \(T\) lúc này có thể coi là tổng trọng số của các phần tử trong \(\operatorname{ETR}(T)\), chỉ cần duy trì tổng trọng số của dãy là có thể thực hiện duy trì số lượng nút cây con. Mà việc duy trì tổng trọng số của dãy là thao tác cổ điển của Treap không xoay.
Tương tự, có thể chuyển các thao tác như giá trị nhỏ nhất cây con thành các thao tác kinh điển của cây cân bằng trên dãy (ví dụ: tìm giá trị nhỏ nhất trên đoạn) rồi duy trì bằng cấu trúc dữ liệu tương ứng.
#include<cassert>#include<cstdint>#include<functional>#include<iostream>#include<map>#include<random>#include<sstream>usingi64=int64_t;usingu64=uint64_t;voidsolve_case(intCase);intmain(){std::ios::sync_with_stdio(false),std::cin.tie(nullptr);intT=1;// std::cin >> T;for(intt=1;t<=T;++t){solve_case(t);}return0;}/** * Dynamic Forest Maintained With Euler Tour Tree. * * As said in reference, link and cut operation of dynamic trees can be * transformed into sequence split and sequence merge operation, which can be * easily maintained using balanced search trees like Treap. * * @reference: Dynamic trees as search trees via euler tours, applied to the * network simplex algorithm by Robert E. Tarjan. * https://link.springer.com/article/10.1007/BF02614369 */classDynamicForest{private:staticstd::mt19937rng_;structNode{intsize_;intpriority_;Node*left_;Node*right_;Node*parent_;intfrom_;intto_;intnum_vertex_;intnum_edge_;Node(intfrom,intto):size_(1),priority_(rng_()),left_(nullptr),right_(nullptr),parent_(nullptr),from_(from),to_(to),num_vertex_(from_==to_?1:0),num_edge_(from_==to_?0:1){}voidMaintain(){size_=1;num_vertex_=from_==to_?1:0;num_edge_=from_==to_?0:1;if(left_){size_+=left_->size_;left_->parent_=this;num_vertex_+=left_->num_vertex_;num_edge_+=left_->num_edge_;}if(right_){size_+=right_->size_;right_->parent_=this;num_vertex_+=right_->num_vertex_;num_edge_+=right_->num_edge_;}}};staticintGetSize(Node*p){returnp==nullptr?0:p->size_;}staticNode*FindRoot(Node*p){if(!p)returnnullptr;while(p->parent_!=nullptr)p=p->parent_;returnp;}staticstd::stringto_string(Node*p){std::stringstreamss;ss<<"Node [\n";std::function<void(Node*)>dfs=[&](Node*p){if(!p)return;dfs(p->left_);ss<<"("<<p->from_<<","<<p->to_<<"),";dfs(p->right_);};dfs(p);ss<<"]\n\n";returnss.str();}Node*AllocateNode(intu,intv){Node*p=newNode(u,v);returnp;}voidFreeNode(Node*&p){if(p){deletep;p=nullptr;}}/* * Dynamic Sequence Maintained using Treap. */classTreap{public:/** * Merge two treap a and b into a single treap, with keys in a less than * keys in b. * * In the other word, concating sequence a and sequence b. */staticNode*Merge(Node*a,Node*b){if(a==nullptr)returnb;if(b==nullptr)returna;if(a->priority_<b->priority_){a->right_=Merge(a->right_,b);a->Maintain();returna;}else{b->left_=Merge(a,b->left_);b->Maintain();returnb;}}/** * Get the number of nodes with keys less than or equal to the key of p. * * In the other word, the the 1-based index of p inside the sequencec * containing p. */staticintGetPosition(Node*p){assert(p!=nullptr);intposition=GetSize(p->left_)+1;while(p){if(p->parent_&&p==p->parent_->right_)position+=GetSize(p->parent_->left_)+1;p=p->parent_;}returnposition;}/** * Split sequence containning p into two sequences, the first one contains * the first k elements, the second one contains the remaining elements. */staticstd::pair<Node*,Node*>Split(Node*p,intk){if(!p)return{nullptr,nullptr};std::pair<Node*,Node*>result;if(GetSize(p->left_)<k){autoright_result=Split(p->right_,k-GetSize(p->left_)-1);p->right_=right_result.first;result.first=p;result.second=right_result.second;}else{autoleft_result=Split(p->left_,k);p->left_=left_result.second;result.first=left_result.first;result.second=p;}p->Maintain();if(result.first)result.first->parent_=nullptr;if(result.second)result.second->parent_=nullptr;returnresult;}/* * Bottom up split treap p into 2 treaps a and b. * - a: a treap containing nodes with position less than or equal to p. * - b: a treap containing nodes with postion greater than p. * * In the other word, split sequence containning p into two sequences, the * first one contains elements before p and element p, the second one * contains elements after p. */staticstd::pair<Node*,Node*>SplitUp2(Node*p){assert(p!=nullptr);Node*a=nullptr,*b=nullptr;b=p->right_;if(b)b->parent_=nullptr;p->right_=nullptr;boolis_p_left_child_of_parent=false;boolis_from_left_child=false;while(p){Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}if(!is_from_left_child){a=Merge(p,a);}else{b=Merge(b,p);}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;}return{a,b};}/* * Bottom up split treap p into 3 treaps a, b and c. * - a: a treap containing nodes with key less than p. * - b: a treap containing nodes with key greater than p. * - c: a treap containing nodes with key equal p. * * In the other word, split sequence containning p into three sequences, the * first one contains elements before p, the second one contains element p, * the third one contains elements after p. */staticstd::tuple<Node*,Node*,Node*>SplitUp3(Node*p){assert(p!=nullptr);Node*a=p->left_;if(a)a->parent_=nullptr;p->left_=nullptr;Node*b=p->right_;if(b)b->parent_=nullptr;p->right_=nullptr;Node*c=p;boolis_p_left_child_of_parent=false;boolis_from_left_child=false;Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;while(p){Node*parent=p->parent_;if(parent){is_p_left_child_of_parent=(parent->left_==p);if(is_p_left_child_of_parent){parent->left_=nullptr;}else{parent->right_=nullptr;}p->parent_=nullptr;}if(!is_from_left_child){a=Merge(p,a);}else{b=Merge(b,p);}is_from_left_child=is_p_left_child_of_parent;p->Maintain();p=parent;}return{a,c,b};}};public:DynamicForest(intn):n_(n),vertices_(n_),tree_edges_(n_){assert(n_>0);for(inti=0;i<n_;++i)vertices_[i]=AllocateNode(i,i);}~DynamicForest(){for(inti=0;i<n_;++i){for(autop:tree_edges_[i]){FreeNode(p.second);}}for(inti=0;i<n_;++i){FreeNode(vertices_[i]);}}voidMakeRoot(intu){Node*vertex_u=vertices_[u];intposition_u=Treap::GetPosition(vertex_u);autop1=Treap::SplitUp2(vertex_u);autoL1=p1.first,L2=p1.second;assert(GetSize(L1)==position_u);Treap::Merge(L2,L1);}voidInsert(intu,intv){assert(!tree_edges_[u].count(v));assert(!tree_edges_[v].count(u));Node*vertex_u=vertices_[u];Node*vertex_v=vertices_[v];Node*edge_uv=AllocateNode(u,v);Node*edge_vu=AllocateNode(v,u);tree_edges_[u][v]=edge_uv;tree_edges_[v][u]=edge_vu;intposition_u=Treap::GetPosition(vertex_u);intposition_v=Treap::GetPosition(vertex_v);autop1=Treap::SplitUp2(vertex_u);autop2=Treap::SplitUp2(vertex_v);autoL11=p1.first,L12=p1.second;autoL21=p2.first,L22=p2.second;assert(GetSize(L11)==position_u);assert(GetSize(L21)==position_v);Node*result=nullptr;result=Treap::Merge(result,L12);result=Treap::Merge(result,L11);result=Treap::Merge(result,edge_uv);result=Treap::Merge(result,L22);result=Treap::Merge(result,L21);result=Treap::Merge(result,edge_vu);}voidDelete(intu,intv){assert(tree_edges_[u].count(v));assert(tree_edges_[v].count(u));Node*edge_uv=tree_edges_[u][v];Node*edge_vu=tree_edges_[v][u];tree_edges_[u].erase(v);tree_edges_[v].erase(u);intposition_uv=Treap::GetPosition(edge_uv);intposition_vu=Treap::GetPosition(edge_vu);if(position_uv>position_vu){std::swap(edge_uv,edge_vu);std::swap(position_uv,position_vu);}autop1=Treap::SplitUp3(edge_uv);autoL1=std::get<0>(p1),uv=std::get<1>(p1);assert(GetSize(L1)==position_uv-1);assert(GetSize(uv)==1);autop2=Treap::SplitUp3(edge_vu);autoL2=std::get<0>(p2),vu=std::get<1>(p2),L3=std::get<2>(p2);assert(GetSize(L2)==position_vu-position_uv-1);assert(GetSize(vu)==1);L1=Treap::Merge(L1,L3);FreeNode(edge_uv);FreeNode(edge_vu);}boolIsConnected(intu,intv){Node*vertex_u=vertices_[u];Node*vertex_v=vertices_[v];returnFindRoot(vertex_u)==FindRoot(vertex_v);}intGetComponentSize(intu){Node*vertex_u=vertices_[u];Node*root_of_vertex_u=FindRoot(vertex_u);returnGetSize(root_of_vertex_u);}intGetComponentNumberOfVertex(intu){Node*vertex_u=vertices_[u];Node*root_of_vertex_u=FindRoot(vertex_u);returnroot_of_vertex_u?root_of_vertex_u->num_vertex_:0;}std::stringto_string()const{std::stringstreamss;ss<<"DynamicForest [\n";std::function<void(Node*)>dfs=[&](Node*p){if(!p)return;dfs(p->left_);ss<<"("<<p->from_<<","<<p->to_<<"),";dfs(p->right_);};for(inti=0;i<n_;++i){if(vertices_[i]->parent_==nullptr){ss<<" Component [";dfs(vertices_[i]);ss<<"]\n";}}for(inti=0;i<n_;++i){for(autop:tree_edges_[i]){if(p.second->parent_==nullptr){ss<<" Component [";dfs(p.second);ss<<"]\n";}}}ss<<"]\n\n";returnss.str();}private:intn_;std::vector<Node*>vertices_;std::vector<std::map<int,Node*>>tree_edges_;};std::mt19937DynamicForest::rng_(std::random_device{}());voidsolve_case(intCase){intn,q;std::cin>>n>>q;DynamicForestt(n+1);std::stringop;intu,v;for(inti=1;i<=q;++i){std::cin>>op>>u>>v;if(op[0]=='A'){t.Insert(u,v);}elseif(op[0]=='Q'){t.Delete(u,v);intans=i64(1)*t.GetComponentNumberOfVertex(u)*t.GetComponentNumberOfVertex(v);t.Insert(u,v);std::cout<<ans<<"\n";}}}
Duy trì thông tin đường đi cây
Có một kỹ thuật khá phổ biến là dựa vào tính chất của thứ tự ngoặc để chuyển đổi thông tin đường đi cây thành thông tin đoạn, sau đó có thể dựa vào cấu trúc dữ liệu duy trì dãy để duy trì thông tin đường đi cây. Tuy nhiên, kỹ thuật này yêu cầu thông tin cần duy trì phải có tính trừ được.
Các thao tác cây động tương ứng với dãy được giới thiệu trước đó có thể làm thay đổi thứ tự trước sau của dấu ngoặc phải so với dấu ngoặc trái trong thứ tự ngoặc, vì vậy khi duy trì các thông tin như tổng điểm nút trên đường đi cây, cần chú ý thêm, thao tác không được làm thay đổi thứ tự trước sau của dấu ngoặc tương ứng, mà điều này có thể cần suy nghĩ lại về thao tác trên dãy tương ứng với thao tác cây động, thậm chí suy nghĩ lại về việc duy trì thứ tự DFS nào.
Ngoài ra, ETT khó duy trì thao tác sửa đổi trên đường đi cây.
Bài toán này thao tác cây động chỉ có đổi cha, có thể coi là xóa cạnh rồi thêm cạnh, nhưng cách này có thể làm thay đổi thứ tự trước sau của các dấu ngoặc tương ứng.
Có thể chuyển trọng số nút thành trọng số cạnh, duy trì thứ tự ngoặc của cây, thao tác đổi cha được chuyển đổi thành việc dịch chuyển toàn bộ dãy ngoặc tương ứng với cây con đến sau dấu ngoặc trái của nút cha.
Dynamic trees as search trees via euler tours, applied to the network simplex algorithm - Robert E. Tarjan
Randomized fully dynamic graph algorithms with polylogarithmic time per operation - Henzinger et al.
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:OI-wiki All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply