hdu 2196 Computer dfs / 树形dp

15 篇文章 0 订阅
10 篇文章 0 订阅

题目链接


(注(给自己看):写这题时 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值