求树的直径的几种方法以及求树上所有点的最远距离
一、dp(由于定义不同,有两种写法,其实dp太灵活了说有几种写法都不奇怪)
一种是记录以每个节点(u)为根,到其子树的最远叶节点的最大距离(dp[u][1])和次大距离(dp[u][0]); 这样以该节点为根的子树的直径即为dp[u][0]+dp[u][1], 对于每个节点取max即为全树的直径;
void dfs(int u, int fa){
for(int i = 0; i < G[u].size(); i++){ int v = G[u][i].to, w = G[u][i].w; if (v == fa) continue; dfs(v, u); if (dp[u][1] < dp[v][1]+w){//维护最大和次大值 dp[u][0] = dp[u][1]; dp[u][1] = dp[v][1]+w; } else if (dp[u][0] < dp[v][1]+w) dp[u][0] = dp[v][1]+w; } ans = max(ans, dp[u][1]+dp[u][0]); }
另一种是只记录最大距离,对于节点u,设dp[u]为以u为根的子树的直径,则dp[u]=max(dp[u], dp[u_son]+dist(u,u_son) ); 依旧对每个节点取max即为全树的直径;
void dfs(int u, int fa) { for(int i = 0; i < G[u].size(); i++){ int v = G[u][i].to, w = G[u][i].w; if (v == fa) continue; dfs(v, u);
ans = max(ans, dp[u] + dp[v] + w);
dp[u] = max(dp[u], dp[v] + w);
}
}
二、两次dfs
我们直接借助hdu2196这道题看看dfs如何求直径以及树上所有点到树上最远点的距离。随便取一个点做dfs,找到以这个点为根的树的最远的叶子节点,之后再以此叶子节点为根做dfs,找到距他最远的节点及距离,这个距离即为树的直径。证明略,可用反证法。
下面说一下这道题中这三次dfs的作用。
第一次:以任意节点为根,根本目的是找到直径的一个端点a。在这个过程中记录了这个起点到各个节点的距离,不过不影响。如果这个节点就是直径的端点,那么很好,如果不是,我们也会在后面更新。
第二次:以第一次找到的这个端点a为根,再做一次dfs,找到另一个端点b,同时更新所有节点到a的距离。(如果第一次中任意选择的那个节点是一个端点的话,两次dfs就可以结束了,但我们不能保证次次碰巧)
第三次dfs以b为根,更新所有节点到b的距离。由于树上某个节点可以达到的最远距离一定是它到直径两个端点的距离的较大值,故算法结束。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 6 const int inf=0x3f3f3f3f; 7 const int maxn=1e4+50; 8 struct edge{ 9 int to,w; 10 edge(int too,int ww):to(too),w(ww){} 11 }; 12 vector<edge>G[maxn]; 13 int dp[maxn],maxl,s; 14 15 void dfs(int u,int f,int len){//len:起点到u的距离 16 if(maxl<len){ 17 s=u; 18 maxl=len; 19 } 20 for(int i=0;i<G[u].size();i++){ 21 int to=G[u][i].to, w=G[u][i].w; 22 if(f==to) continue; 23 dfs(to,u,len+w); 24 dp[to]=max(dp[to],len+w); 25 } 26 } 27 int main(){ 28 ios::sync_with_stdio(0); 29 cin.tie(0); cout.tie(0); 30 int n; 31 while(cin>>n){ 32 memset(dp,0,sizeof(dp)); 33 for(int i=0;i<=n;i++) G[i].clear(); 34 for(int i=2,to,w;i<=n;i++){ 35 cin>>to>>w; 36 G[i].push_back(edge(to,w)); 37 G[to].push_back(edge(i,w)); 38 } 39 s=0; maxl=0; 40 dfs(1,0,0); 41 dfs(s,0,0); 42 dfs(s,0,0); 43 for(int i=1;i<=n;i++) cout<<dp[i]<<endl; 44 } 45 return 0; 46 }
另外这题有一种dp的写法,与上文所说的第一种dp有点像,有很多前辈的blog说的很详细了,就不赘述了。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 6 const int inf=0x3f3f3f3f; 7 const int maxn=1e4+50; 8 struct Edge{ 9 int to,w; 10 Edge(int too,int ww){to=too,w=ww;} 11 }; 12 vector<Edge>G[maxn]; 13 int dp[3][maxn],id[maxn]; 14 //id[u]: u在u的子树中得到最大距离时,所经过的第一个孩子结点 15 //dp[u][0]: u在u的所有子树中获得的最长距离 16 //dp[u][1]: u在u的所有子树中获得的次长距离 17 //dp[u][2]: u通过父亲获得的最长距离 18 void dfs1(int u,int f){ 19 for(int i=0;i<G[u].size();i++){//get最长 20 int to=G[u][i].to,w=G[u][i].w; 21 if(to==f) continue; 22 dfs1(to,u); 23 if(dp[0][u]<dp[0][to]+w){ 24 dp[0][u]=dp[0][to]+w; 25 id[u]=to; 26 } 27 } 28 for(int i=0;i<G[u].size();i++){//get次长 29 int to=G[u][i].to,w=G[u][i].w; 30 if(to==f||id[u]==to) continue; 31 dp[1][u]=max(dp[1][u],dp[0][to]+w); 32 } 33 } 34 void dfs2(int u,int f){ 35 for(int i=0;i<G[u].size();i++){ 36 int to=G[u][i].to,w=G[u][i].w; 37 if(to==f) continue; 38 if(to==id[u]) 39 dp[2][to]=max(dp[2][u],dp[1][u])+w; 40 else 41 dp[2][to]=max(dp[2][u],dp[0][u])+w; 42 43 dfs2(to,u); 44 } 45 } 46 47 int main(){ 48 ios::sync_with_stdio(0); 49 cin.tie(0); cout.tie(0); 50 int n; 51 while(cin>>n){ 52 memset(dp,0,sizeof(dp)); 53 for(int i=0;i<=n;i++) G[i].clear(); 54 for(int i=2,to,w;i<=n;i++){ 55 cin>>to>>w; 56 G[i].push_back(Edge(to,w)); 57 G[to].push_back(Edge(i,w)); 58 } 59 dfs1(1,0); 60 dfs2(1,0); 61 for(int i=1;i<=n;i++) 62 cout<<max(dp[0][i],dp[2][i])<<endl; 63 } 64 return 0; 65 }
Stay Hungry, Stay Foolish