点(树)分治
0.引言
對于樹上問題,有許多特殊的求解方法,如:樹鏈剖分。點分治算法也是其中之一,常用于解決樹上路徑問題。
1.0.問題的引入
給定一棵樹,求這棵樹的直徑(樹上最長鏈長度,n<=10^5)
解法1.兩遍dfs:先任選一個起點t,通過dfs遍歷整個樹,找到最長路的終點v,再從v進行dfs,第二次dfs找到節點u,樹的直徑即為u->v的路徑長度。(此非本文內容,證明不再撰述。)
解法2.暴力點分治
首先考慮枚舉算法,對于一個節點u,我們可以先枚舉所有經過u的鏈的長度s。
對于一個u的子樹T,記f[T]=Max(dist(u,v)),v∈T。
顯然,f[T]=max(f[A])+1(A是T的子樹)。
則s=Max(f[T])+Max(f[ T' ])+1,T'≠T。
也就是說,s為子樹中最遠點v1與次遠v2點的距離,且v1,v2不在同一個子樹內,是即為經過u的最大鏈長度。
于是,我們可以求出u的每個子樹的最遠點距離,將最大和次大距離相加即是s。
此時,發現答案中與u有關的部分都求解完畢,只需要分治求解u的子樹的s,最后合并答案即可。
這暴力枚舉算法的時間復雜度是多少呢?
顯然,這個算法的時間復雜度為O(sigma(size))的,也就是所有節點的子樹大小之和。
發現這個算法在 ?數據退化為一條鏈的情況下,時間復雜度為O(n^2)。
?
如何優化???
我們需要盡量保持樹的平衡,那么我們當然選擇將子樹的“重心”作為子樹的根。
(重心:找到一個點,其所有的子樹中最大的子樹節點數最少,那么這個點就是這棵樹的重心)
我們發現選取重心之后,分治樹節點u的每個子樹大小一定小于u樹的大小的一半。
于是時間復雜度為O(n log n),且有時根本達不到n log n的時間。
?
(
一般有兩種合并方式:
1.子樹依次合并,統計答案。
2.求出所有鏈的答案,再減去不包含這個節點的答案。
)
完美!!
1.1.點分治的實現
void get_center(int u,int father,int Size) //Size表示整棵樹的大小 {tree[u]={1,0}; //u節點初始化 for (int i=head[u];i;i=e[i].next){int v=e[i].to;if (size[v]||v==father) continue;get_center(v,u,Size); //dfstree[u].size+=tree[v].size; //更新u子樹大小 tree[u].heavy_son=get_max(tree[u].heavy_son,tree[v].size); //更新u的子樹的最大值 }tree[u].heavy_son=get_max(tree[u].heavy_son,Size-tree[u].size); if (tree[u].heavy_son<MIN) {root=u,MIN=tree[u].heavy_son}; } void divide(int u) {ans+=solve(u,0),visit[u]=1; //求所有鏈的答案 for (int i=head[u];i;i=e[i].next) {int v=e[i].to;if (visit[v]) continue;ans-=solve(v,e[i].d); //減去不經過u的答案 Smer=size[v];root=0;MIN=INF; get_center(v,0); divide(v);} }總結
 
                            
                        - 上一篇: 仙茅的功效与作用、禁忌和食用方法
- 下一篇: 干漆的功效与作用、禁忌和食用方法
