资料:http://www.cnblogs.com/celia01/archive/2012/07/30/2615842.html
http://www.cnblogs.com/wuyiqi/archive/2012/04/08/2437424.html
树的直径:树中最长路径的长度。
方法:假设最长路径为s-t ,对于任意一点u,找一条以u为起点的最长路径,这条路径的终点必为s、t 中的一点,从该点开始找最长路径,得到的路径就是树中的最长路径,也就是树的直径。
实现:对于任意一点为起点运用dfs,找到最长路,并记录下终点,在用这个终点作为起点,再做一次dfs,再找到的那一条最长路的长度就是树的直径。(当然这里求最长路的时候运用dfs或者是bfs都是可以的)。
对于这个方法的证明:
1)假设u是s-t路径中的一点,第一次终点必是s、t中的一个,如果不是,假设最远点是T那么dis(u,T) > dis(u,s) && dis(u,T) > dis(u,t) ,这与最长路径是s-t是矛盾的。 所以第一次终点必是s、t中的一个。那么这个方法是显然可以的。
2)若u不为s、t中的一点。
首先直到u如果走到了s-t路径上的话,那么之后的路径也在s-t上,终点必为s或t。
现在分类:
1、 如果u走到s-t路径上的某一点。接下来就会走到s或t上。按照这个方法也是可以求出最长路径的。
2、如果没有走到s-t路径上的一点,也就是u走的最远路径假设为u-T与s-t路径没有交点,那么dis(u,T) > dis(u,s) && dis(u,T) > dis(u,t),对于第二个式子同时加上dis(u,s)则
dis(T,u)+dis(u,s)=dis(u,T)>dis(u,s)+dis(u,t)=dis(u,t),
与之前s-t为最长的假设矛盾。所以这种情况不存在。
得证。
poj 1985 直接求树的直径
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define M 100009
#define INF 0x3f3f3f3f
typedef pair<int,int> pii;
vector<pii> G[M];
int n,m;
int maxlen,s;
void dfs(int st,int pre,int len)
{
if(maxlen < len)
{
s = st;
maxlen = len;
}
for(int i = 0;i < G[st].size();i++)
{
pii tmp = G[st][i];
if(tmp.first == pre) continue;
dfs(tmp.first,st,len + tmp.second);
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d %d",&n,&m) == 2)
{
for(int i = 0;i <= n;i++) G[i].clear();
for(int i = 0;i < m;i++)
{
int a,b,c;
char ch;
scanf("%d %d %d %c",&a,&b,&c,&ch);
G[a].push_back(make_pair(b,c));
G[b].push_back(make_pair(a,c));
}
maxlen = -1;
dfs(1,-1,0);
maxlen = -1;
dfs(s,-1,0);
printf("%d\n",maxlen);
}
return 0;
}
poj 1849
题意: 让两个人遍历这个树,不需要返回起点,求花费经过的最短的路径。
思路:如果让一个人遍历这个树并且要返回起点的话,那么花费一定是整个树所有边权值和的两倍。让两个人遍历树,不用返回起点的话,最小花费就是用所以边权值和的两倍 - 树的直径
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define M 100009
#define INF 0x3f3f3f3f
struct node
{
int v,w;
};
vector<node> G[M];
int n,s;
int ss,maxlen;
void dfs(int st,int pre,int len)
{
for(int i = 0;i < G[st].size();i++)
{
node tmp = G[st][i];
if(tmp.v != pre) dfs(tmp.v,st,len + tmp.w);
else
{
if(maxlen < len)
{
maxlen = len;
ss = st;
}
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d %d",&n,&s) == 2)
{
int sum = 0;
for(int i = 0;i < n-1;i++)
{
int a,b,ww;
scanf("%d %d %d",&a,&b,&ww);
G[a].push_back(node{b,ww});
G[b].push_back(node{a,ww});
sum += 2*ww;
}
maxlen = -INF;
dfs(s,-1,0);
maxlen = -INF;
dfs(ss,-1,0);
printf("%d\n",sum - maxlen);
}
return 0;
}
hdu 2196
题意:求每一个点为起点的最长路径的长度。
同样运用树的直径的方法,运用两次dfs 都找最长路, 如果在这个过程中路径的长度大于这个点为起点的最长路径的长度,那么就更新,第一次dfs后就会到达直径的一个端点处。再来一次就可以更新除了这个端点之外其他点的最长路径,并且到达的终点是直径的另一个端点。但是这个后之前以那个直径的端点的最长路径还没有更新,再从第二次dfs 的终点为起点dfs一次,就可以更新之前那个直径的端点了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define M 100009
#define INF 0x3f3f3f3f
typedef pair<int,int> pii;
vector<pii> G[M];
int n,maxlen,dis[M];
int s;
void dfs(int st,int pre,int len)
{
if(maxlen < len)
{
maxlen = len;
s = st;
}
if(dis[st] < len) dis[st] = len;
for(int i = 0;i < G[st].size();i++)
{
pii tmp = G[st][i];
if(tmp.first == pre) continue;
dfs(tmp.first,st,len + tmp.second);
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d",&n) == 1)
{
for(int i = 0;i <= n;i++)
{
G[i].clear();
dis[i] = -1;
}
for(int i = 0;i < n-1;i++)
{
int a,b;
scanf("%d %d",&a,&b);
G[i+2].push_back(make_pair(a,b));
G[a].push_back(make_pair(i+2,b));
}
maxlen = -1;
dfs(1,-1,0);
maxlen = -1; //每次都要记得还原maxlen避免失效
dfs(s,-1,0);
maxlen = -1;
dfs(s,-1,0); //再一次dfs 以保证更新之前的终点即树的直径的一个端点
for(int i = 1;i <= n;i++) printf("%d\n",dis[i]);
}
return 0;
}