(rt,這只是一篇小小的總結,以便將來的回顧,並不詳細講)
以前也學習過啟發式合併,大概就是像樹形dp一樣在dfs上,將兒子的資訊向父親轉移,容器是map,將兒子的資訊邊轉移邊更新答案,轉移之後便將兒子的容器清空,防止空間超限。不過對於本人而言,雖然思路較為簡便,但是因為有用到map的迭代器,所以這種寫法寫起來較為繁瑣。
最近學了一種基於dfs序的一種的啟發式合併,特點就是:暴力。
看似暴力。
演算法的流程大概是這樣:
1、dfs將一棵樹建好,將節點的size、dfs序、重兒子、該dfs序對應的節點這些資訊處理好(其他的資訊具體問題具體分析)。
2、進入solve函式,先去解決非重兒子,然後將這些非重兒子的資訊暴力清空。
3、接下來解決重兒子,這次不清空。
4、然後再將非重兒子的資訊再暴力新增。
5、將該節點的資訊新增進容器。
6、回答關於這個節點的問題。
7、返回。
注意:這裡的新增是利用dfs序進行的。
流程還是有點繁瑣,那就看**吧。
void build(int p,int pre,int d)
r[p]=dfn;
}void del(int p)
void ins(int p)
void solve(int p)
if(to)solve(to);
if(!p)return;
for(int i=0;iint nx=e[p][i];
if(nx!=to)ins(nx);
}sum[dep[p]]++;
for(int i=0,sz=ask[p].size();isum[ask[p][i].k]-1;
}
相信看到**的時候就在想,這不是暴力嗎?
這其實和普通的啟發式合併的核心是一樣的:普通的啟發式合併的複雜度是o(nlogn),是因為每次合併是將小集合並到大集合,對於小集合來說,合併後,集合的size至少擴大到原來的2倍,那麼合併的次數一定小於等於logn。然後看有n個點,所以所有點合併的次數就是nlogn。考慮在map合併自帶的複雜度,以及清空記憶體的複雜度,這樣近似暴力的啟發式合併的複雜度也就近似o(nlogn)了。
同理,每乙個重兒子合併的次數也是logn。
所以dsu on tree的複雜度同樣是近似o(nlogn)。
當然對於存資訊的sum不一定是普通的陣列,可能是map,可能是樹狀陣列這類既支援插入又支援刪除的容器,有適合的資料結構,而不是用資料結構去套題。
這種演算法能夠解決關於詢問一棵樹的子樹的相關資訊的問題。
當然,演算法是好的,關鍵是在足夠理解的基礎上能夠用起來!
dsu on tree 樹上啟發式合併
詳解 dsu on tree 樹上啟發式合併 演算法總結 習題 經典例題 題意 一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。dsu on tree簡介 在o n 2 的暴力做法中,我們用cnt記錄每種顏色出現的次數,對於每個結點,遍歷這棵子樹上的所...
dsu on tree 樹上啟發式合併
樹上啟發式合併 用於處理離線查詢的子樹問題 第一次先求出重兒子 第二次dfs的過程中,先計算輕兒子,貢獻不保留,再計算重兒子,貢獻保留 最後再加上輕兒子的貢獻,計算出當前節點的值 複雜度為o nlogn 每個節點都有乙個顏色編號 計算一棵以1為根的樹的以每個節點為根的子樹中顏色最多的顏色編號和 這題...
dsu on tree 樹上啟發式合併
對於一顆靜態樹,o nlogn 時間內處理子樹的統計問題。是一種優雅的暴力。很顯然,樸素做法下,對於每顆子樹對其進行統計的時間複雜度是平方級別的。考慮對樹進行乙個重鏈剖分。雖然都基於重鏈剖分,但不同於樹剖,我們維護的不是樹鏈。對於每個節點,我們先處理其輕兒子所在子樹,輕子樹在處理完後消除其影響。然後...