樹的直徑
樹的直徑(Diameter)是指樹上的最長簡單路。
直徑的求法:兩遍搜索 (BFS or DFS)
任選一點w為起點,對樹進行搜索,找出離w最遠的點u。
以u為起點,再進行搜索,找出離u最遠的點v。則u到v的路徑長度即為樹的直徑。
簡單證明:
如果w在直徑上,那么u一定是直徑的一個端點。反證:若u不是端點,則從直徑另一端點到w再到u的距離比直徑更長,與假設矛盾。
如果w不在直徑上,且w到其距最遠點u的路徑與直徑一定有一交點c,那么由上一個證明可知,u是直徑的一個端點。
如果w到最遠點u的路徑與直徑沒有交點,設直徑的兩端為S與T,那么(w->u)>(w->c)+(c->T),推出(w->u)+(S->c)+(w->c)>(S->c)+(c->T)=(S->T)與假設矛盾。
因此w到最遠點u的路徑與直徑必有交點。
S-----------c-----------T
|
w------u
樹的重心
何謂重心
樹的重心:找到一個點,其所有的子樹中最大的子樹節點數最少,那么這個點就是這棵樹的重心,刪去重心后,生成的多棵樹盡可能平衡。
樹的重心可以通過簡單的兩次搜索求出,第一遍搜索求出每個結點的子結點數量son[u],第二遍搜索找出使max{son[u],n-son[u]-1}最小的結點。
實際上這兩步操作可以在一次遍歷中解決。對結點u的每一個兒子v,遞歸的處理v,求出son[v],然后判斷是否是結點數最多的子樹,處理完所有子結點后,判斷u是否為重心。

1 struct CenterTree{ 2 int n; 3 int ans; 4 int siz; 5 int son[maxn]; 6 void dfs(int u,int pa){ 7 son[u]=1; 8 int res=0; 9 for (int i=head[u];i!=-1;i=edges[i].next){ 10 int v=edges[i].to; 11 if (v==pa) continue; 12 if (vis[v]) continue; 13 dfs(v,u); 14 son[u]+=son[v]; 15 res=max(res,son[v]-1); 16 } 17 res=max(res,n-son[u]); 18 if (res<siz){ 19 ans=u; 20 siz=res; 21 } 22 } 23 int getCenter(int x){ 24 ans=0; 25 siz=INF; 26 dfs(x,-1); 27 return ans; 28 } 29 }Cent;
還有一種方法,雖然不能保證求出來的一定是重心,但是能找到一個結點,而這個以這個結點為根的話,所有的子樹的結點數量都不會超過總結點數的一半。
同樣是先搜索出son[u],從某個假設的根開始向下找,如果有子結點v,使son[v]>son[u]/2,就以v作為新的根,重復執行,直到沒有滿足條件的子結點。
【POJ 1655 Balancing Act】
給定一棵樹,求樹的重心的編號以及重心刪除后得到的最大子樹的節點個數,如果個數相同就選取編號最小的。
直接套模板可解,注意要取編號最小的根。
樹的點分治
何謂分治
分治,指的是分而治之,即將一個問題分割成一些規模較小的相互獨立的子問題,以便各個擊破。
我們常見的是在一個線性結構上進行分治,而分治算法在樹結構上的運用,稱之為樹的分治算法。
分治往往與高效聯系在一起,而樹的分治正是一種用來解決樹的路徑問題的高效算法。
樹的點的分治:首先選取一個點將無根樹轉為有根樹,再遞歸處理每一顆以根結點的兒子為根的子樹。
首先我們考慮如何選取點。對於基於點的分治,我們選取一個點,要求將其刪去后,結點最多的樹的結點個數最小,這個點就是樹的重心。
在基於點的分治中每次我們都會將樹的結點個數減少一半,因此遞歸深度最壞是 O(NlogN) 的,在樹是一條鏈的時候達到上界。
【例 POJ 1741 Tree】
給你一棵TREE,以及這棵樹上邊的距離。問有多少對點它們兩者間的距離小於等於K。
我們知道一條路徑要么過根結點,要么在一棵子樹中,這啟發了我們可以使用分治算法。
只要先求出經過根結點的路徑數,再遞歸的求經過所有子結點的路徑數即可。
下面來分析如何處理路徑過根結點的情況。
我們先用一次搜索求出根的所有子結點到根的距離並將其放入一個數組中,復雜度O(n)。
將這個距離數組排序,復雜度O(nlogn)。
這樣就將問題轉化為了,求一個數組A中,和小於等於K的元素對個數有多少。
由於數組有序,對於區間[L,R],易知若A[L]+A[R]>K,那么區間內沒有滿足條件的元素對。若A[L]+A[R]<=K,則以L為左端點的點對數有R-L個。
我們從1開始枚舉L,當前R不滿足條件,就令R-1,否則統計以L為左端點的點對數,令L-1。
用一個線性掃描的掃描可以解決,復雜度O(n)。
最終我們得到了所有子結點到根的距離和小於等於K的點對數。
然而這個並不是最終解,因為我們要求的是經過根的路徑,而從一個子樹到達根結點又回到同一個子樹的路徑是不能被計入統計的,所以我們要把多余的點對從結果中減去。
我們只要對每一個子樹,求出同一個子樹中的結點到根結點又回到子樹的路徑和小於等於K的點對數,然后從答案中減去即可。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 using namespace std; 7 const int INF=0x3f3f3f3f; 8 const int maxn=11000; 9 const int maxm=21111; 10 struct EdgeNode{ 11 int to; 12 int w; 13 int next; 14 }edges[maxm]; 15 int head[maxn],edge; 16 bool vis[maxn]; 17 18 void init(){ 19 edge=0; 20 memset(head,-1,sizeof(head)); 21 memset(vis,0,sizeof(vis)); 22 } 23 void addedge(int u,int v,int w){ 24 edges[edge].w=w,edges[edge].to=v,edges[edge].next=head[u],head[u]=edge++; 25 } 26 int n,K; 27 struct CenterTree{ 28 int n; 29 int ans; 30 int siz; 31 int son[maxn]; 32 void dfs(int u,int pa){ 33 son[u]=1; 34 int res=0; 35 for (int i=head[u];i!=-1;i=edges[i].next){ 36 int v=edges[i].to; 37 if (v==pa) continue; 38 if (vis[v]) continue; 39 dfs(v,u); 40 son[u]+=son[v]; 41 res=max(res,son[v]-1); 42 } 43 res=max(res,n-son[u]); 44 if (res<siz){ 45 ans=u; 46 siz=res; 47 } 48 } 49 int getCenter(int x){ 50 ans=0; 51 siz=INF; 52 dfs(x,-1); 53 return ans; 54 } 55 }Cent; 56 int data[maxn]; 57 int dis[maxn]; 58 int Len; 59 int ans; 60 void getArray(int u,int pa){ 61 data[++Len]=dis[u]; 62 for (int i=head[u];i!=-1;i=edges[i].next){ 63 int v=edges[i].to; 64 int w=edges[i].w; 65 if (v==pa) continue; 66 if (vis[v]) continue; 67 dis[v]=dis[u]+w; 68 getArray(v,u); 69 } 70 } 71 int calc(int u,int now){ 72 dis[u]=now; 73 Len=0; 74 getArray(u,-1); 75 sort(data+1,data+Len+1); 76 int res=0; 77 int l=1,r=Len; 78 while (l<r){ 79 if (data[r]+data[l]<=K){ 80 res+=(r-l); 81 l++; 82 } 83 else r--; 84 } 85 return res; 86 } 87 void solve(int u){ 88 ans+=calc(u,0); 89 vis[u]=true; 90 for (int i=head[u];i!=-1;i=edges[i].next){ 91 int v=edges[i].to; 92 int w=edges[i].w; 93 if (vis[v]) continue; 94 ans-=calc(v,w); 95 Cent.n=Cent.son[v]; 96 int rt=Cent.getCenter(v); 97 solve(rt); 98 } 99 } 100 int main() 101 { 102 while (~scanf("%d%d",&n,&K)){ 103 if (n==0&&K==0) break; 104 init(); 105 for (int i=1;i<n;i++){ 106 int x,y,z; 107 scanf("%d%d%d",&x,&y,&z); 108 addedge(x,y,z); 109 addedge(y,x,z); 110 } 111 ans=0; 112 Cent.n=n; 113 int root=Cent.getCenter(1); 114 solve(root); 115 printf("%d\n",ans); 116 } 117 return 0; 118 } 119 /** 120 20 10 121 1 2 3 122 2 3 4 123 3 4 5 124 4 5 6 125 5 6 7 126 6 7 8 127 7 8 9 128 8 9 10 129 9 10 11 130 10 11 12 131 11 12 13 132 12 13 14 133 13 14 15 134 14 15 16 135 15 16 17 136 16 17 18 137 17 18 19 138 18 19 20 139 19 20 21 140 **/