(注(给自己看):写这题时 dfs 的写法还很丑陋(P的痕迹太重...),在之后几篇树形dp的题中会有改观(大概可以称得上))
题意:
给定一棵树,结点编号为 1 ~ n, 求得树上每一点与跟它距离最远的点之间的距离
思路:
法一:
很快联想到树的直径,因为求树的直径就是:
任取一个点做一次深搜,找到的最远的点必然是树直径的一个端点;再从该点开始做一次深搜,找到的最远的点必然是树直径的另一个端点。
由此得知,与树上任一点距离最远的点 必然是 树的两个直径当中的一个,
只需要做三次深搜即可,
第一次找到一个端点 u,
第二次记录 u 到树上其他点的距离,并找到另一个端点 v,
第三次记录 v 到树上其他点的距离,
AC代码如下:
#include <cstdio>
#include <cstring>
#define maxn 10010
struct Edge {
int u, v, dist, ne;
Edge(int xx = 0, int yy = 0, int dd = 0, int nn = 0): u(xx), v(yy), dist(dd), ne(nn) {}
}edge[maxn * 2];
int depth[maxn], depth2[maxn], next[maxn], tot, n;
bool vis[maxn];
inline max(int a, int b) { return a > b ? a : b; }
void add(int x, int y, int dist) {
Edge e(x, y, dist, next[x]);
edge[tot] = e;
next[x] = tot++;
}
void dfs(int x, int dep, int* depth) {
vis[x] = true;
depth[x] = dep;
int num = next[x];
while (num != -1) {
Edge e = edge[num];
if (!vis[e.v]) dfs(e.v, dep + e.dist, depth);
num = e.ne;
}
}
void work() {
tot = 0;
memset(next, -1, sizeof(next));
for (int i = 2; i <= n; ++i) {
int y, d;
scanf("%d%d", &y, &d);
add(i, y, d);
add(y, i, d);
}
memset(vis, 0, sizeof(vis));
dfs(1, 0, depth);
int mmax = 1;
for (int i = 2; i <= n; ++i) {
mmax = depth[mmax] > depth[i] ? mmax : i;
}
int l = mmax;
memset(vis, 0, sizeof(vis));
memset(depth, 0, sizeof(depth));
dfs(l, 0, depth);
mmax = 1;
for (int i = 2; i <= n; ++i) {
mmax = depth[mmax] > depth[i] ? mmax : i;
}
int r = mmax;
// printf("%d %d\n", l, r);
memset(vis, 0, sizeof(vis));
memset(depth2, 0, sizeof(depth));
dfs(r, 0, depth2);
// for (int i = 1; i <= n; ++i) {
// printf("%d ", depth[i]);
// }
// for (int i = 1; i <= n; ++i) {
// printf("%d ", depth2[i]);
// }
for (int i = 1; i <= n; ++i) {
printf("%d\n", max(depth[i], depth2[i]));
}
}
int main() {
// freopen("2196.in", "r", stdin);
while (scanf("%d", &n) != EOF) work();
return 0;
}
法二:
后来看了别人的blog和代码,得知这是一道树形 dp 的经典题,
任取一个点为根,做两次 dp,一次自下而上,一次自上而下,
自下而上记录 每个点 在 以其为根的子树 中到叶子节点的最长距离 f[u][0],
顺便记录1)次长距离 f[u][1] (之后需要用到) 2)最长距离需经过哪个与之直接相邻的顶点 v
自上而下记录 每个点往上走的最长距离 f[u][2].
对于有向边 u -> v,
1. 显然自下而上很容易,
f[u][0] = max(f[v][0] + e.dist)
2. 至于自上而下,乍一想并不很直观,因为比如说 点 v 往上走,可能在点 u 就往下拐弯了,可能在点 u 的父亲 uu 才往下拐弯,可能在 uu 的父亲 uuu 才拐弯,等等等等,这可怎么办呢?
事实上,上述的很多种情况无非两种,一种是在点 u 处向下拐弯,一种是继续往上走,
第一种,即为 f[u][0] 的含义;第二种,即为 f[u][2] 的含义
慢着!
第一种情况中,万一 u 向下走的最长路本身就经过点 v 了呢?那不是重复了吗?
对啊,这就是为什么我们要记录次长路的原因所在了
如果不经过,f[v][2] = max(f[u][0], f[u][2]) + e.dist,
如果经过,f[v][2] = max(f[u][1], f[u][2]) + e.dist,
另外,还有一点要注意的是,我们说的次长路,事实上并不一定真的是 u 的子树中与 u 距离第二最大的点,
而是不经过 v 的与 u 距离最大的点,即这个点不可能通过 v 的子树中的次长路转移过来,这一点要明确
AC代码如下:
#include <cstdio>
#include <cstring>
#define maxn 10010
struct Edge {
int u, v, dist, ne;
Edge(int xx = 0, int yy = 0, int dd = 0, int nn = 0): u(xx), v(yy), dist(dd), ne(nn) {}
}edge[maxn * 2];
int f[maxn][3], next[maxn], adj[maxn], tot, n;
bool vis[maxn];
inline max(int a, int b) { return a > b ? a : b; }
void add(int x, int y, int dist) {
Edge e(x, y, dist, next[x]);
edge[tot] = e;
next[x] = tot++;
}
void dfs(int u) {
vis[u] = true; f[u][0] = f[u][1] = 0;
int num = next[u];
while (num != -1) {
Edge e = edge[num];
int v = e.v;
num = e.ne;
if (vis[v]) continue;
dfs(v);
if (e.dist + f[v][0] > f[u][0]) {
f[u][1] = f[u][0];
f[u][0] = e.dist + f[v][0];
adj[u] = v;
}
else if (e.dist + f[v][0] > f[u][1]) f[u][1] = e.dist + f[v][0];
}
}
void dfs2(int u) {
vis[u] = true;
int num = next[u];
while (num != -1) {
Edge e = edge[num];
int v = e.v;
num = e.ne;
if (vis[v]) continue;
if (adj[u] == v) f[v][2] = f[u][1] > f[u][2] ? f[u][1] + e.dist : f[u][2] + e.dist;
else f[v][2] = f[u][0] > f[u][2] ? f[u][0] + e.dist : f[u][2] + e.dist;
dfs2(v);
}
}
void work() {
tot = 0;
memset(next, -1, sizeof(next));
for (int i = 2; i <= n; ++i) {
int y, d;
scanf("%d%d", &y, &d);
add(i, y, d);
add(y, i, d);
}
memset(vis, 0, sizeof(vis));
dfs(1);
memset(vis, 0, sizeof(vis));
dfs2(1);
// for (int i = 1; i <= n; ++i) {
// printf("%d ", f[i][2]);
// }
// printf("\n");
for (int i = 1; i <= n; ++i) printf("%d\n", max(f[i][0], f[i][2]));
}
int main() {
// freopen("2196.in", "r", stdin);
while (scanf("%d", &n) != EOF) work();
return 0;
}