直徑 : 在圓上兩點(不相交)之間最遠的距離就是咱們一般所說的直徑。 樹的直徑 : 樹上最遠的兩個節點之間的距離就被稱爲樹的直徑,鏈接這兩點的路徑被稱爲樹的最長鏈。
一、樹形 DP 二、兩次 BFS 或者 兩次 DFS
優勢 : 能夠有效處理 負邊權 缺點 : 對於記錄路徑的信息效率較低 簡單分析 : 先經過遞歸的方式到葉子底部,而後經過自底向上的方式進行更新距離,找到最長路徑。 (看下圖,能夠獲得這棵樹的直徑是通過根節點 1 的 路徑最長的鏈 5 -> 2 -> 1 和 通過根節點 1 的路徑 次長鏈 3 -> 6 -> 1 二者之和 由此可得:樹的直徑 = (通過某個節點的) 最長鏈 + 次長鏈) -- 是路徑長度哦
實現過程: 設 D[x] 表示從節點 x 出發走向以 x 爲根的子樹,可以到達的最遠距離。 設 x 的子節點爲 y1,y2....yt,edge(x,y)表示邊權,顯然有: D[x] = max(D[yi] + edge(x,yi))(i 的範圍是 1 - t) 也就是說,從 根節點出發,找到本身的最小輩,而後從最小輩向根節點更新,找一個最長的路徑鏈。 只要子節點是最長的,那麼咱們更新到根節點時,這條鏈毫無疑問也就是最長的。 咱們在找某個節點的最長鏈會發現一個問題,就是當前節點有有幾個子節點,有一個咱們沒得選,當有多個時咱們就須要選擇最長的。 拿上面的圖來講, 2 號節點有 兩個子節點,咱們就須要進行比較一下,100 > 2 + 30 ,因此咱們應該選擇 5 -> 2 這條鏈。
具體代碼:node
void dp(int x) { vis[x] = 1; for(int i = head[x]; i ; i = Next[i]) { int y = ver[i]; if(vis[y]) continue; // 判斷是否已經通過該節點 dp(y); // 繼續向下尋找子節點 ans = max(ans,dist[x] + dist[y] + edge[i]); // 枚舉從 x 節點出發的全部邊,找一個最遠的路徑(看上面的 2 號節點)(dist[x] 是當前目前已知的最長的, // 可是 x 可能有多個分支,因此須要枚舉找最長的或者次長的,造成通過該節點的直徑) dist[x] = max(dist[x],dist[y] + edge[i]); // 通過枚舉後 dist[x] 就不必定是當前最長的的,因此須要更新一下。 }
優勢 : 能夠經過一個新的數組記錄路徑信息(例如父節點與子節點之間的關係) 缺點 : 沒法處理 負邊權(遇到 負邊權 涼涼) 實現過程 : 一、從任意一個節點出發,經過 BFS 或 DFS 對樹進行一次遍歷,求出與出發點距離最遠的節點,記爲 p。 二、從節點 p 出發,經過 BFS 或 DFS 再進行一次遍歷,求出與 p 距離最遠的節點,記爲 q。 (p 是一個節點的最遠的一個端點,那麼從 p 出發的最遠的端點就是直徑的另外一個端點) 爲何沒法處理負邊權?
看上面這個圖: 若是按照 DFS 或者 BFS 咱們第一次 找到的最遠距離的節點是 2 , 而後從 2 出發
到達的最遠距離的節點是 1 ,因此獲得的樹的直徑長度是 1 ,但咱們從圖中很容易看出來樹的直徑最長
應該是 2.(用樹形 DP 的話從下向上就能夠獲得最長的樹的直徑的長度)ios
具體代碼 :
BFS: #include <queue> #include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; int head[maxn * 2],edge[maxn * 2],Next[maxn * 2],ver[maxn * 2]; int vis[maxn],dist[maxn]; int n,p,q,d; int tot = 0; int maxd = 0; int main(void) { int BFS(int u); void add(int u,int v,int w); scanf("%d",&n); for(int i = 1; i < n; i ++) { scanf("%d%d%d",&p,&q,&d); add(p,q,d); // 創建無向圖 add(q,p,d); } int u = BFS(1); int s = BFS(u); printf("第一次遍歷獲得的節點 : %d\n",u); printf("第二次遍歷獲得的節點 : %d\n",s); return 0; } void add(int u,int v,int w) { ver[ ++ tot] = v,edge[tot] = w; Next[tot] = head[u],head[u] = tot; return ; } int BFS(int u) { queue<int>Q; while(!Q.empty()) Q.pop(); memset(vis,0,sizeof(vis)); // 每次遍歷的時候記得對數組進行 Clear memset(dist,0,sizeof(dist)); Q.push(u); int x,max_num = 0; while(!Q.empty()) { x = Q.front(); Q.pop(); vis[x] = 1; for(int i = head[x]; i ; i = Next[i]) { int y = ver[i]; if(vis[y]) continue; vis[y] = 1; dist[y] = dist[x] + edge[i]; // 從上向下走,因此須要進行累加(這是與 樹形 DP最大的不一樣) if(dist[y] > maxd ) { // 更新 值 和 節點編號 maxd = dist[y]; max_num = y; } Q.push(y); // 每一個新的節點都要加入到隊列中,有可能與該節點相連的路徑是比較長的 } } return max_num; }
DFS : #include <vector> #include <cstdio> #include <string> #include <cstring> #include <string.h> #include <iostream> #include <algorithm> #define x first #define y second using namespace std; const int maxn = 1e5 + 10; typedef pair<int,int> P ; // 用 pair<int,int>來保存部分信息,相對於 結構體來講更加方便一點 vector<P> G[maxn]; int dist[maxn]; int n,p,q,d; int main(void) { void solve(); scanf("%d",&n); for(int i = 1; i < n; i ++) { scanf("%d%d%d",&p,&q,&d); G[p].push_back(make_pair(q,d)); G[q].push_back(make_pair(p,d)); } solve(); return 0; } void DFS(int u,int father,int value) { dist[u] = value; // 這種方式就不用進行對數組 Clear 了 for(int i = 0; i < G[u].size(); i ++) { if(G[u][i].x != father) { DFS(G[u][i].x,u,value + G[u][i].y); } } return ; } void solve() { DFS(1,-1,0); int u = 1; for(int i = 1; i <= n; i ++) { // 遍歷尋找最大值 if(dist[i] > dist[u]) { u = i; } } int x = u; DFS(u,-1,0); // 第二次進行找另外一個端點 for(int i = 1; i <= n; i ++) { if(dist[i] > dist[u]) { u = i; } } int s = u; printf("第一次遍歷獲得的最遠的節點編號 : %d\n",x); printf("第二次遍歷獲得的最遠的節點編號 : %d\n",s); return ; }
yxc 的視頻講解:https://www.acwing.com/video/710/ 秦淮岸大佬的講義:https://www.acwing.com/blog/content/319 最重要的是 AS 的細心講解。
題目連接:https://www.acwing.com/problem/content/1209/)
好久之前,T王國空前繁榮。 爲了更好地管理國家,王國修建了大量的快速路,用於鏈接首都和王國內的各大城市。 爲節省經費,T國的大臣們通過思考,制定了一套優秀的修建方案,使得任何一個大城市都能從首都直接或者經過其餘大城市間接到達。 同時,若是不重複通過大城市,從首都到達每一個大城市的方案都是惟一的。 J是T國重要大臣,他巡查於各大城市之間,體察民情。 因此,從一個城市快馬加鞭地到另外一個城市成了J最常作的事情。 他有一個錢袋,用於存放往來城市間的路費。 聰明的J發現,若是不在某個城市停下來修整,在連續行進過程當中,他所花的路費與他已走過的距離有關,在走第x公里到第x+1公里這一公里中(x是整數),他花費的路費是x+10這麼多。也就是說走1公里花費11,走2公里要花費23。 J大臣想知道:他從某一個城市出發,中間不休息,到達另外一個城市,全部可能花費的路費中最可能是多少呢? 輸入格式 輸入的第一行包含一個整數 n,表示包括首都在內的T王國的城市數。 城市從 1 開始依次編號,1 號城市爲首都。 接下來 n−1 行,描述T國的高速路(T國的高速路必定是 n−1 條)。 行三個整數 Pi,Qi,Di,表示城市 Pi 和城市 Qi 之間有一條雙向高速路,長度爲 Di 公里。 輸出格式 輸出一個整數,表示大臣J最多花費的路費是多少。 數據範圍 1≤n≤105, 1≤Pi,Qi≤n, 1≤Di≤1000
5 1 2 2 1 3 1 2 4 5 2 5 4
135
求路上的最大花費,最大花費由與距離有關,因此求出距離就能夠解出這道題目。 實際上就是爲樹的直徑是多少,只不過這道題在最後計算結果的時候還須要注意一下。
看上圖和題意,咱們能夠得知: 距離爲 5 的花費爲
s = 5;
money = s * 10 + (s * (s + 1)) / 2算法
具體代碼:數組
算法 1 : 樹形 DP #include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; int head[maxn],edge[maxn],Next[maxn],ver[maxn]; int dist[maxn],vis[maxn]; int tot = 0, ans = 0; int n,p,q,d; int main(void) { void dp(int x); void add(int u,int v,int w); scanf("%d",&n); for(int i = 1; i < n; i ++) { scanf("%d%d%d",&p,&q,&d); add(p,q,d); add(q,p,d); } ans = 0; dp(1); printf("%lld\n", ans * 10 + ans * (ans + 1ll ) / 2); return 0; } void add(int u,int v,int w) { ver[++ tot] = v,edge[tot] = w; Next[tot] = head[u],head[u] = tot; return ; } void dp(int x) { vis[x] = 1; for(int i = head[x]; i ; i = Next[i]) { int y = ver[i]; if(vis[y]) continue; dp(y); ans = max(ans,dist[x] + dist[y] + edge[i]); dist[x] = max(dist[x],dist[y] + edge[i]); } return ; }
算法 2 : 兩次 BFS #include <queue> #include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; int head[maxn * 2],edge[maxn * 2],Next[maxn * 2],ver[maxn * 2]; int vis[maxn],dist[maxn]; int n,p,q,d; int tot = 0; int maxd = 0; int main(void) { int BFS(int u); void add(int u,int v,int w); scanf("%d",&n); for(int i = 1; i < n; i ++) { scanf("%d%d%d",&p,&q,&d); add(p,q,d); add(q,p,d); } int u = BFS(1); int s = BFS(u); printf("%lld\n", maxd * 10 + maxd * (maxd + 1ll ) / 2); return 0; } void add(int u,int v,int w) { ver[ ++ tot] = v,edge[tot] = w; Next[tot] = head[u],head[u] = tot; return ; } int BFS(int u) { queue<int>Q; while(!Q.empty()) Q.pop(); memset(vis,0,sizeof(vis)); memset(dist,0,sizeof(dist)); Q.push(u); int x,max_num = 0; while(!Q.empty()) { x = Q.front(); Q.pop(); vis[x] = 1; for(int i = head[x]; i ; i = Next[i]) { int y = ver[i]; if(vis[y]) continue; vis[y] = 1; dist[y] = dist[x] + edge[i]; if(dist[y] > maxd ) { maxd = dist[y]; max_num = y; } Q.push(y); } } return max_num; }
算法 3 : 兩次 DFS #include <vector> #include <cstdio> #include <string> #include <cstring> #include <string.h> #include <iostream> #include <algorithm> #define x first #define y second using namespace std; const int maxn = 1e5 + 10; typedef pair<int,int> P ; struct node { int id,w; }; vector<P> G[maxn]; int dist[maxn]; int n,p,q,d; int main(void) { void solve(); scanf("%d",&n); for(int i = 1; i < n; i ++) { scanf("%d%d%d",&p,&q,&d); G[p].push_back(make_pair(q,d)); G[q].push_back(make_pair(p,d)); } solve(); return 0; } void DFS(int u,int father,int value) { dist[u] = value; for(int i = 0; i < G[u].size(); i ++) { if(G[u][i].x != father) { DFS(G[u][i].x,u,value + G[u][i].y); } } return ; } void solve() { DFS(1,-1,0); int u = 1; for(int i = 1; i <= n; i ++) { if(dist[i] > dist[u]) { u = i; } } DFS(u,-1,0); for(int i = 1; i <= n; i ++) { if(dist[i] > dist[u]) { u = i; } } int s = dist[u]; printf("%lld\n", s * 10 + s * (s + 1ll ) / 2); return ; }