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,觉得如果树有多条直径,这个结论也应该是正确的。