题目链接;
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);