题目链接;
POJ 1985 Cow Marathon
HDU 2196 Computer
题意:
两道题其实都是求树的直径。
分析:
树的直径:树中所有最短路径的最大值。
定理:
- 在一个连通的无项无环图中,以任意结点出发所能到达的最远结点,一定是该图直径的端点之一。
证明:假设直径是
δ(s,e)
,任意结点为
x
,其最远能到达的结点为
①如果
x
是直径
②:如果
x
不是直径
- 树的直径等于以树直径上任意一点为根的有根树,其左子树的高度+1,再加上其右子树高度+1。
下面的代码只是部分。
求树直径的方法:
1.根据定理1可以以任意结点出发找的其最远距离结点,这个结点必然是直径端点之一,再以这个结点出发找到求得其最远距离,可以使用
dfs
或
bfs
。
void bfs(int u) //bfs版本,采用链式前向星存边
{
queue<int> que;
vis[u] = 1;
que.push(u);
while (!que.empty()) {
int cur = que.front();
que.pop();
for (int i = head[cur]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (vis[v]) continue;
dp[v] = dp[cur] + w;
vis[v] = 1;
que.push(v);
}
}
}
int solve()
{
memset(vis, 0, sizeof(vis));
memset(dp, 0, sizeof(dp));
bfs(1);
int tmp = 0, st; //st是直径的一个端点
for (int i = 1; i <= n; ++i) {
if (dp[i] > tmp) {
tmp = dp[i];
st = i;
}
}
memset(dp, 0, sizeof(dp));
memset(vis, 0, sizeof(vis));
bfs(st);
tmp = 0;
for (int i = 1; i <= n; ++i) {
if (dp[i] > tmp) tmp = dp[i];
}
return tmp; //树的直径
}
void dfs(int u) //dfs版本
{
vis[u] = 1;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (vis[v]) continue;
dp[v] = dp[u] + w;
dfs(v);
}
}
//调用时只需要把上面bfs()版本中solve()函数bfs()改为dfs()
2.另外一种求树的直径的方法。我们可以想办法求出每个节点到其他节点的最远距离。这个最远距离可能来自节点的子树也可能来自节点的父亲节点。我们先用两个数组
far[]
和
ffar[]
保存节点通过子树可以得到的最远距离和次远距离,并用
id[]
保存获得最远距离时该子树的根节点编号。假设节点
v
的父亲节点是
//far[]最远子树距离,ffar[]次远子树距离,father[]通过父亲节点获得的最远距离
//id[]从子树获得最远距离时该子树的根节点编号
void dfs1(int u, int p) //p是u的父亲节点
{ //从子树获得最远距离和次远距离
if (far[u] != -1) return ;
far[u] = ffar[u] = 0;
// 找子树最远距离
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (v == p) continue; //只能从u的子树获得,不能返回父亲节点
dfs1(v, u);
if (w + far[v] > far[u]) {
far[u] = far[v] + w;
id[u] = v;
}
}
// 找子树次远距离
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (v == p || v == id[u]) continue; //不能是父亲节点和最远距离节点
if (w + far[v] > ffar[u]) {
ffar[u] = far[v] + w;
}
}
}
void dfs2(int u, int p) //p是u的父亲节点
{ //从父亲节点获得最远距离
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (v == p) continue;
if (v == id[u]) { //如果v是u最远距离时的子树根节点
father[v] = max(father[u], ffar[u]) + w;
} else {
father[v] = max(father[u], far[u]) + w;
}
dfs2(v, u);
}
}
//主函数部分
memset(far, -1, sizeof(far));
memset(ffar, -1, sizeof(ffar));
memset(father, -1, sizeof(father));
dfs1(1, 0); //对每个点求其到子树上节点的最远距离和次远距离
father[1] = 0;
dfs2(1, 0); //对每个点求其经过父节点可到达的最远距离
int diameter = 0;
for (int i = 1; i <= n; ++i) {
longest[i] = max(father[i], far[i]); //longest[i]时节点i可以到达的最远距离
if (longest[i] > diameter) diameter = longest[i];
//printf("longest[%d] = %d\n", i, longest[i]);
}
printf("%d\n", diameter);