dp一直弱死了,樹型dp很多基本的題都不會,最近在刷樹型dp的題,把關於樹的最長路的思想總結一下:
樹的直徑:樹中距離最遠的兩點間的距離。
下面說幾道題:
hdu 2196:對於樹上(雙向邊)的每一個節點求出與其距離最遠的點的距離。
這個主要用的思想是兩次dfs:一次dfs將無向圖轉化為有跟樹(所以一開是一定要是建雙向邊,不然很可能wa或者tle,記錄過程中可以開數組記入父親節點,也可以在dfs遞推過程中以棧的形式記錄)求出每個跟節點到其所有的葉子節點的最遠距離f[i]和g[i]。再一次dfs求出能夠由父親節點轉化得到的最大距離h[i],求h[i]的過程中就有可能用到f[i]和g[i]了,因為如果i節點在其父親j節點的最遠距離f[j]中,那么f[i]就只能由g[j]或者h[j]得到,不然就由f[j]或者h[j]得到,具體可能說的不是特別清。兩個dfs綜合起來的復雜度只有O(E)
poj 1985:求樹的直徑。個人覺得大概有3種方法。
第一種是如上題hdu2196的寫法,求出每個點的最遠距離最后取最大值,這樣不會增加太多的復雜度,因為每次dfs也都知識O(E)的復雜度。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 using namespace std; 8 const int maxn = 40005; 9 int f[maxn], g[maxn], h[maxn], longest[maxn]; 10 vector<int> son[maxn], w[maxn]; 11 12 int dfs(int root, int pre){ 13 int i, j; 14 int est = 0, esti=-1, er=0; 15 if(f[root]!=-1) return f[root]; 16 if(son[root].empty()) return f[root] = 0; 17 for(i=0;i<son[root].size();i++){ 18 if(pre!=son[root][i]){ 19 if(dfs(son[root][i],root)+w[root][i]>est){ 20 est = f[son[root][i]]+w[root][i]; 21 esti = i; 22 } 23 } 24 } 25 longest[root] = esti; 26 for(i=0;i<son[root].size();i++){ 27 if(pre!=son[root][i]){ 28 if(f[son[root][i]]+w[root][i]>er&&i!=longest[root]){ 29 er = f[son[root][i]]+w[root][i]; 30 } 31 } 32 } 33 g[root] = er; 34 return f[root] = est; 35 } 36 37 void dfs1(int root, int pre){ 38 int i, j; 39 for(i=0;i<son[root].size();i++){ 40 if(pre!=son[root][i]){ 41 if(i!=longest[root]){ 42 h[son[root][i]] = max(h[root],f[root])+w[root][i]; 43 } 44 else{ 45 h[son[root][i]] = max(h[root],g[root])+w[root][i]; 46 } 47 dfs1(son[root][i],root); 48 } 49 } 50 } 51 52 void Init(int n){ 53 int i; 54 for(i=0;i<=n;i++){ 55 f[i] = g[i] = h[i] = longest[i] = -1; 56 son[i].clear(); 57 w[i].clear(); 58 } 59 } 60 int main(){ 61 int i, j, k, n, m, ans; 62 int x1, x2, l; 63 char opt; 64 while(~scanf("%d%d",&n,&m)){ 65 Init(n); 66 for(i=0;i<m;i++){ 67 scanf("%d%d%d",&x1,&x2,&l); 68 scanf(" %c",&opt); 69 son[x1].push_back(x2);w[x1].push_back(l); 70 son[x2].push_back(x1);w[x2].push_back(l); 71 } 72 for(i=1;i<=n;i++){ 73 if(f[i]==-1){ 74 f[i] = dfs(i,-1); 75 h[i] = 0; dfs1(i,-1); 76 } 77 } 78 ans = 0; 79 for(i=1;i<=n;i++){ 80 ans = max(ans,max(f[i],h[i])); 81 } 82 printf("%d\n",ans); 83 } 84 return 0; 85 }
第二種是利用了樹的直徑的一個性質:距某個點最遠的葉子節點一定是樹的某一條直徑的端點。這樣就可以首先任取一個點,bfs求出離其最遠的點,在用同樣的方法求出離這個葉子節點最遠的點,此時兩點間的距離就是樹的直徑。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #include<queue> 8 using namespace std; 9 const int maxn = 40005; 10 vector<int> son[maxn], w[maxn]; 11 bool vis[maxn], viss[maxn]; 12 int f[maxn]; 13 int bfs(int root){ 14 int i, j, k; 15 int ans = root, maxx = 0; 16 queue<int> q; 17 memset(vis,0,sizeof(vis)); 18 memset(f,0,sizeof(f)); 19 q.push(root); 20 vis[root] = 1;f[root] = 0;viss[root] = 1; 21 while(!q.empty()){ 22 root = q.front(); 23 q.pop(); 24 for(i=0;i<son[root].size();i++){ 25 if(vis[son[root][i]]==0){ 26 q.push(son[root][i]); 27 vis[son[root][i]] = 1;viss[son[root][i]] = 1; 28 f[son[root][i]] = f[root]+w[root][i]; 29 if(maxx<f[son[root][i]]){ 30 maxx = f[son[root][i]]; 31 ans = son[root][i]; 32 } 33 } 34 } 35 } 36 return ans; 37 } 38 int solve(int root){ 39 int u, v; 40 u = bfs(root); 41 v = bfs(u); 42 return f[v]; 43 } 44 int main(){ 45 int i, j, k, n, m; 46 int x1, x2, l, u; 47 int res; 48 char opt; 49 while(~scanf("%d%d",&n,&m)){ 50 for(i=0;i<=n;i++){ 51 son[i].clear(); 52 w[i].clear(); 53 } 54 for(i=0;i<m;i++){ 55 scanf("%d%d%d",&x1,&x2,&l); 56 scanf(" %c",&opt); 57 son[x1].push_back(x2);w[x1].push_back(l); 58 son[x2].push_back(x1);w[x2].push_back(l); 59 } 60 res = 0; 61 memset(viss,0,sizeof(vis)); 62 for(i=1;i<=n;i++){ 63 if(viss[i]==0){ 64 res = max(res,solve(i)); 65 } 66 } 67 printf("%d\n",res); 68 } 69 return 0; 70 }
ps:搜索也可以用dfs的方法搜,不過個人覺得bfs雖然更長,但比較好寫,不容易出錯。
第三種方法應該也可以算是樹的直徑的一個性質了吧,樹的直徑的長度一定會是某個點的最長距離f[i]與次長距離g[i]之和。最后求出max{f[i]+g[i]}就可以了,用到方法1中的第一個dfs就行了

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 using namespace std; 8 const int maxn = 40005; 9 int f[maxn], g[maxn], longest[maxn]; 10 vector<int> son[maxn], w[maxn]; 11 int dfs(int root, int pre){ 12 int i, j; 13 int est = 0, esti=-1, er=0; 14 if(f[root]!=-1) return f[root]; 15 if(son[root].empty()) return f[root] = 0; 16 for(i=0;i<son[root].size();i++){ 17 if(pre!=son[root][i]){ 18 if(dfs(son[root][i],root)+w[root][i]>est){ 19 est = f[son[root][i]]+w[root][i]; 20 esti = i; 21 } 22 } 23 } 24 longest[root] = esti; 25 for(i=0;i<son[root].size();i++){ 26 if(pre!=son[root][i]){ 27 if(f[son[root][i]]+w[root][i]>er&&i!=longest[root]){ 28 er = f[son[root][i]]+w[root][i]; 29 } 30 } 31 } 32 g[root] = er; 33 return f[root] = est; 34 } 35 36 void Init(int n){ 37 int i; 38 for(i=0;i<=n;i++){ 39 f[i] = g[i] = longest[i] = -1; 40 son[i].clear(); 41 w[i].clear(); 42 } 43 } 44 int main(){ 45 int i, j, k, n, m, ans; 46 int x1, x2, l; 47 char opt; 48 while(~scanf("%d%d",&n,&m)){ 49 Init(n); 50 for(i=0;i<m;i++){ 51 scanf("%d%d%d",&x1,&x2,&l); 52 scanf(" %c",&opt); 53 son[x1].push_back(x2);w[x1].push_back(l); 54 son[x2].push_back(x1);w[x2].push_back(l); 55 } 56 for(i=1;i<=n;i++){ 57 if(f[i]==-1){ 58 f[i] = dfs(i,-1); 59 } 60 } 61 ans = 0; 62 for(i=1;i<=n;i++){ 63 ans = max(ans,f[i]+g[i]); 64 } 65 printf("%d\n",ans); 66 } 67 return 0; 68 }
樹的直徑應該就是以上幾種方法了吧,不過從poj1985的三種方法可以得出用搜索的方法求某個點的最遠的點的距離了,就是先對任意一個點求距離其最遠的頂點,最后可以得到一條樹的直徑的兩個端點,以這兩個端點開始去遍歷整棵樹,兩個端點到每個點的距離較大值就會是這個點在樹上能夠走的最遠距離。手畫了幾個sample,覺得如果樹有多條直徑,這個結論也應該是正確的。