演算法 樹上啟發式合併演算法

2021-07-29 12:45:33 字數 1552 閱讀 3234

樹上啟發式合併演算法是啟發式合併演算法在樹上的應用。下面我直接通過乙個例子來講解這個演算法。

例:給定一棵有根樹,樹的結點編號為1~n,根結點為結點1。結點i有顏色col[i],其中1≤col[i]≤n。要求回答m個詢問,每個詢問回答顏色c在子樹u中出現多少次。

顯然要將查詢離線處理,即對子樹u的查詢都「掛」到結點u上。我們用cnt[c]表示顏色c出現的次數,那麼一種容易想到的暴力做法如下:

0.cnt陣列初始化為0;

1.從結點1開始dfs整棵樹。dfs至結點u時,按如下步驟處理掛在子樹u內的結點上的查詢:

(1)dfs結點u的各個兒子;

(2)遍歷一遍子樹u,在遍歷的同時更新cnt陣列;

(3)給出掛在結點u上的查詢的答案;

(4)遍歷一遍子樹u,在遍歷的同時更新cnt陣列(把對cnt陣列的貢獻抹去)。

顯然這個暴力做法的時間複雜度為o(n²)。容易想到的乙個優化是在步驟1(0)中u的最後乙個兒子對cnt陣列的貢獻可以保留著,這樣步驟1(1)就不需要遍歷以該兒子為根的子樹。這個優化有多大效果呢?事實上,如果我們適當改變dfs順序,使得被dfs的最後乙個兒子對應的子樹是諸位兒子中最大的,那麼這個優化可以把時間複雜度降至o(nlgn)。

帶這種優化的暴力做法就是樹上啟發式合併。在遍歷時,用bool變數keep來表示子樹u對cnt陣列的貢獻是否要保留。演算法步驟如下:

0.cnt陣列初始化為0;

1.從結點1開始dfs整棵樹。dfs至結點u時,按如下步驟處理掛在子樹u內的結點上的查詢:

(1)找到結點u的對應最大子樹的兒子bc;

(2)dfs結點u的各個兒子,其中結點bc是u的最後乙個被遍歷到的兒子,遞迴處理時若該子結點非bc則keep賦值為0,否則賦值為1;

(3)遍歷一遍子樹u,但不遍歷子樹bc,在遍歷的同時更新cnt陣列;

(4)給出掛在結點u上的查詢的答案;

(5)若keep為0,遍歷一遍子樹u,在遍歷的同時更新cnt陣列(把對cnt陣列的貢獻抹去)。

下面簡單地計算演算法的時間複雜度:演算法的耗時來自於對查詢的回答與各結點對cnt陣列的操作。由於每次查詢都是o(1)的,所以查詢的總複雜度為o(m),可以忽略。考慮結點u對cnt陣列的操作次數,設u的祖先從近到遠依次為w_1,w_2,...,w_t。處理子樹u時,結點u第一次對cnt陣列進行操作,而結點u下一次對cnt陣列進行操作,發生在u下一次不在某個祖先的兒子子樹中最大的那棵中時,我們來說明這樣的事只能發生o(lgn)次。若u不在w_k的兒子子樹中最大的那棵中,則有size(w_k-1)*2≤size(w_k),由於size(w_t)=n,所以這樣的事(u不在w_k的兒子子樹中最大的那棵中)只能發生o(lgn)次。綜上,各節點對cnt陣列的操作次數為o(nlgn)。

下面給出樹上啟發式合併的關鍵**:

void dfs1(int u,int fu) //sz[u]為子樹u的大小,st[u]與ft[u]分別為子樹u的dfs開始時間與結束時間,ver[time]為time時刻dfs的結點編號

{ sz[u]=1;st[u]=++t_c;ver[t_c]=u;

for (int i=0;i習題與解答(待更新)

codeforce741d

參考資料

樹上啟發式合併

解決樹上統計問題,o n log n o n log n o n lo g n 可以結合線段樹等資料結構維護深度上的資訊 部落格 入門題 const int maxn 1e5 7 const int mod 1e9 7 ll n,m,u,v,mx,sum vector int mp maxn int...

樹上啟發式合併

樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你 n 2 變成嚴格 n log 解決一些類似 樹上數顏色,樹上查眾數 這樣的問題 首先你要知道暴力為什麼是 n 2 的 以這個圖為例 每次你從乙個節點開始向下搜,你從1節點搜到3,搜完這個子樹然後你需要把3存的col等資訊刪去再遍歷另乙個子樹才是...

樹上啟發式合併總結

某一天發現一道樹上啟發式合併裸題,但我不會寫 學習並刷了兩天的題,是時候來寫個總結了 樹上啟發式合併 dsu on tree 是乙個在o n logn o nlogn o nlog n 時間內解決許多樹上問題的有力演算法。但它的中心其實是 暴力!沒錯,它正是由暴力優化而來。我們先看一道例題 cf60...