淺談樹的直徑
定義:
樹的直徑指樹上最長鏈(最遠點對)
求解:
樹的直徑存在兩種求解方式均為O(n)復雜度,其各有優劣
1.貪心法
任取一點作為起點,找到樹上距離該點的最遠點,記作st,再以st為起點,找到樹上距離st最遠的點,記作ed,st至ed即為樹的直徑。
(找最遠點操作DFS和BFS均可)
優點:起點與終點方便獲得。
缺點:負邊權就GG。
inline void dfs(int now,int fa,int deep) { if(deep>res) { res=deep; ed=now; } f[now]=fa; dep[now]=deep; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now,deep+a[i].val); } } dfs(1,0,0); res=0; st=ed; dfs(st,0,0);
2.樹型DP
任取一點作為起點,記錄樹上每一點向下的最遠距離和非嚴格次遠距離,直徑長度即為每一點二者之和的最大值。
優點:能處理負邊權。
缺點:起點終點難以記錄。
inline void dfs(int now,int fa) { for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now); res=max(res,dp[now]+dp[t]+a[i].val); dp[now]=max(dp[now],dp[t]+a[i].val); } }
性質:
1.直徑兩端點一定是葉子節點。
2.距任意點最遠點一定是直徑的端點,據所有點最大值最小的點一定是直徑的中點。
3.兩棵樹相連,新直徑的兩端點一定是原四個端點中的兩個
4.兩棵樹相連,新直徑長度最小為max(max(直徑1,直徑2),半徑1+半徑2+新邊長度 ) (設k為直徑中最接近中點的節點,半徑=max(tot-d[k],d[k]))
5.一棵樹上接一個葉子結點,直徑最多改變一個端點
6.若一棵樹存在多條直徑,多條直徑交於一點,且交點是直徑的嚴格中點(中點可能在某條邊內)
例題:
前言:樹的直徑姿勢點主要考察各種性質的應用,靈活運用各種性質即可切題。
題目概述:有一棵樹,要求我們在樹上加上1~2兩條邊,使遍歷每一條路時經過的路徑最短,關於路徑,有以下幾個要求
1.新建的邊必須正好經歷一次
2.新邊可以是自環
3.遍歷后回到起點
分析:既然要回到起點,在無環情況下,每條邊必然要走兩遍,路徑長度為2*n-2;
對於k=1的情況,一個貪心的想法必然是讓樹上最長邊形成一個環,所以求一遍樹的直徑即可,設樹的直徑長度為res,路徑長度變為2*n-2-res+1;
而對於k=2的情況,在加完第一條邊后,必然要找第二條最長的邊
然而在找第二條最長邊的時候我們發現:若第二條邊與第一條邊有重疊部分,那么重疊部分就要再走一次,相當於減少了第一次的貢獻
同時,由於建立新邊是為了防止一條邊被第二次遍歷,第一條最長邊選中的部分本身遍不用第二次遍歷,所以該邊對於最長邊的貢獻本就應是零
綜合本來的貢獻和減少的第一次貢獻,改變對於第二次求最長邊的貢獻應該是邊權的負值XD,此時我們應該將第一條最長邊的邊權全部取反,再找一條最長邊。
在取反后,樹上遍有了負邊權,我們需要采取樹型DP求第二條最長邊,而且由於需要取反第一條最長邊,我們需要第一條最長邊的路徑,第一條適合用dfs求最長邊
總結:該題目綜合考察了DFS求樹的直徑和樹型DP求樹的直徑的優點。
題目概述:在一顆樹上選取一段區間,長度小於m,使得所有節點到該區間的最大值最小。
分析:由性質2:據任意點最大值最小的點一定是直徑的中點 可知:中點必然入選。
再考慮將中點擴展為區間:據任意點最遠點一定是直徑的中點可知,若擴展為區間,在直徑上擴展一定是最優的
可反證:若存在另一點到直徑上一點距離大於直徑端點到直徑上一點距離,那么將會產生新的直徑。
現在問題轉化為在直徑上選取一段區間長度小於m,使得任意一點到這段區間的最大值最小;
我們可以預處理出其他節點到達直徑的最遠距離,一次為l,以直徑長度為r,二分答案(判斷根據性質2只需判斷該區間到直徑端點距離是否小於mid)
近一步我們可以雙指針法直接求答案
注意一個特判:若m>=直徑長度,直接輸出l。
總結:重點考察了性質2和相關應用。
題目概述:在樹上斷開一條邊連到別處,使得新樹的直徑最小,求直徑最小值。
分析:為了改變新樹直徑,斷開的邊必然在原樹直徑上,找到一條直徑,枚舉斷開哪一條邊。(若有多條直徑也沒法管)
根據性質4:新樹直徑最小值為max(max(子樹1直徑,子樹2直徑),子樹1半徑+子樹2半徑+斷開邊長度),在所有新樹直徑中取最小值即可。
總結:考察性質4運用。
題目概述:給定一棵樹,求所有直徑都共同經過的邊的數量。
分析:根據性質6:所有直徑必然交於中點,我們只要分析中點在邊內和在節點上的情況。
情況1:中點在邊內:ans=1,對於該邊的兩個節點做一次以下操作:
處理出每個點能夠到達的最大深度,記作d[now],若存在0個或1個以上的子節點滿足d[t]+邊權==d[now]則該節點向下不存在直徑必經邊。
若僅存在1個子節點d[t]+邊權==d[now],++ans,再對該子節點做一個相同操作。
情況2:中點在點上:處理出每個點能夠到達的最大深度,扔進堆里,取前三大的點比較,設為a,b,c。
若不存在c或a=b>c,ans=2,a,b,做一次上述操作。
若a=b=c,則不存在必經邊。
該題目略難以理解(至少弱雞作者這么認為的QWQ),所以將代碼放上,但是由於各大OJ該題數據普遍過水,不存在中點在節點上的數據,作者並不能保證代碼的正確性,若有大神發現不妥之處歡迎指正
#include<bits/stdc++.h> using namespace std; #define int long long typedef pair<int,int> p; priority_queue<p> q; inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,st,res,tot,now,ans; int f[200010],dep[200010],d[200010]; int head[200010],cnt; struct point { int nxt,to,val; }a[500010]; inline void add(int x,int y,int z) { a[++cnt].nxt=head[x]; a[cnt].to=y; a[cnt].val=z; head[x]=cnt; } inline void dfs(int now,int fa,int deep) { f[now]=fa; dep[now]=deep; d[now]=0; if(deep>res) { res=deep; st=now; } for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now,deep+a[i].val); d[now]=max(d[now],d[t]+a[i].val); } } inline void work(int x,int y) { dfs(x,y,0); while("miao") { int t=0,sum=0; for(int i=head[x];i;i=a[i].nxt) { if(a[i].to==y) continue; if(d[x]==d[a[i].to]+a[i].val) { t=a[i].to; ++sum; } } if(sum^1) break; y=x; x=t; ++ans; } } signed main() { n=read(); for(int x,y,z,i=1;i<n;++i) { x=read(),y=read(),z=read(); add(x,y,z); add(y,x,z); } dfs(1,0,0); res=0; dfs(st,0,0); printf("%lld\n",res); while(2*dep[f[st]]>=res) st=f[st]; if(2*dep[st]==res) { dfs(st,0,0); for(int i=head[st];i;i=a[i].nxt) { int t=a[i].to ; q.push(p(d[t]+a[i].val,t)); } p a=q.top(); q.pop(); p b=q.top(); q.pop(); if(!q.empty()) { ans=2; work(a.second,st); work(b.second,st); } else { p c=q.top(); if(c.first==a.first) { ans=0; } else { ans=2; work(a.second,st); work(b.second,st); } } } else { ans=1; work(st,f[st]); work(f[st],st); } printf("%lld\n",ans); return 0; }
總結:考察性質6以及直徑中點相關知識。