洛谷 P3174 【[HAOI2009]毛毛蟲】
阿新 • • 發佈:2020-09-15
一看這道題立馬想到了樹的直徑
還沒有兩遍\(dfs\)求樹的直徑的做法,趕快來水一發
已經會樹的直徑的大佬可以跳過這一段\(qwq\)
樹的直徑:樹上最長的一條鏈
具體求法有兩種,一種是樹形\(DP\),這裡就不細講了,我著重講下兩遍\(dfs\)求樹的直徑的做法,可以說很經典了。
做法: 先從任意一點出發,找到離出發點的最遠點,然後再從找到的那個點出發,找一次最遠點,這兩點就是樹的直徑的兩個端點。
以下是證明(也不是必須會證明啦,不想看的\(dalao\)可以跳過)
\(dis\)的意思是兩點直接的長度,圖片中兩點之間的長度是不確定的,比如\(1\),\(2\)之間可能有多個節點,只是簡潔點畫
接下來回到這道題,跟樹的直徑是不是就有點聯絡了,樹的直徑的\(dfs\)的時候每延申\(1\)個點,我們只需要加上\(1\)個長度,而這道題應該這樣加:
假設我們\(dfs\)從\(x\)開始,那麼我們一開始初始的總和為\(dis[x]\)(\(dis\)為這個點連到的所有點),設\(nx\)為\(x\)的兒子,那麼往\(nx\)展的時候,應該加上\(dis[nx]-2\),因為當搜到\(nx\)的時候,首先他自己這個點,已經被他的父親\(x\)加過了,而且\(nx\)還連了一個\(x\),\(x\)被父親自己加過了,所以不需要再加了,所以減\(2\)
思路講完了,下面是程式碼時間~
#include <bits/stdc++.h> using namespace std; int n , m , ans1 , ans2 , maxx , tot , f = 0; //ans1為第一次的最遠點,ans2為第二次的最遠點 int dis[300010]; vector<int> e[300010]; stack<int> s; void dfs1(int x , int sum , int fa){ if(maxx < sum){ maxx = sum; ans1 = x; } for(int i = 0; i < e[x].size(); i++){ int nx = e[x][i]; if(nx == fa) continue; dfs1(nx , sum + dis[nx] - 2 , x); } } void dfs2(int x , int sum , int fa){ if(tot < sum){ ans2 = x; tot = sum; } for(int i = 0; i < e[x].size(); i++){ int nx = e[x][i]; if(nx == fa) continue; dfs2(nx , sum + dis[nx] - 2 , x); } } int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) dis[i]++; //自己也算連到了 for(int i = 1; i <= m; i++){ int x , y; cin >> x >> y; e[x].push_back(y); e[y].push_back(x); dis[x]++; dis[y]++; } dfs1(1 , dis[1] , 0); dfs2(ans1 , dis[ans1] , 0); cout << tot; return 0; }