树的直径

无权树的直径

对于一棵含有 n n n个节点 n − 1 n-1 n1条边的树,其直径定义为树上的最长链(最长路径)。而且容易得到,再连接首尾两点,会形成一个最大环。

求无权树的直径的两种思路:DFS和树形DP。

DFS

思路:从任意一点 u u u先做一次 d f s dfs dfs找到最远的一点 t a i l tail tail,在从 t a i l tail tail做一次 d f s dfs dfs找到最远的距离,而第二次的距离就是树的直径。

证明:假设 s − > t s->t s>t这条路径为树的直径。现有结论,从任意一点 u u u出发搜到的最远的点一定是 s s s t t t中的一点,然后再从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两遍深搜就可以找出树的最长路。

以树的边为基础DFS,假设当前的边为 u − > v u -> v u>v ,初始时边为 0 0 0到根节点,其中节点 0 0 0是虚构的节点。每次将最大深度 m a x d maxd maxd和当前深度比较,如果小则更新 m a x d maxd maxd并记录 v v v

const int maxn = 1e4 + 10;
vector<int> G[maxn];
int maxd, head, tail;

int dfs(int u, int v, int cur) {
    if (maxd < cur) {
        maxd = cur;
        tail = v;
    }
    for (int i = 0; i < G[v].size(); i++) {
        if (u != G[v][i]) {
            dfs(v, G[v][i], cur + 1);
        }
    }
}

int main() {
	maxd = 0;
	dfs(0, 1, 0);
	maxd = 0;      //清空最大深度
    head = tail;    //第一次求出的起点并保存
	dfs(0, tail, 0);  //tail变成了最长路的终点
    return 0;
}

树形DP

对于任意节点 i i i,经过 i i i的最长路就是连接 i i i的两棵不同子树 u u u v v v的最深叶子的路径。

状态转移:设 d ( i ) d(i) d(i)表示以根为节点 i i i的子树中根到叶子的最大距离, j j j i i i的子节点,那么状态转移方程为: d ( i ) = m a x { d ( j ) } + 1 d(i)=max\{d(j)\}+1 d(i)=max{d(j)}+1

求出所有的 d ( r o o t ) d(root) d(root)后,设 d ( s o n [ r o o t ] ) d(son[root]) d(son[root])值前两大的子节点分别为 u , v u,v u,v,那么树的直径就是 d ( u ) + d ( v ) + 2 d(u)+d(v)+2 d(u)+d(v)+2

vector<int> G[maxn];
int d[maxn];
int ans;

void dfs(int u, int fa) {
    d[u] = 0;
    for (auto v: G[u]) {
        if (v != fa) {
            dfs(v, u);
            d[u] = max(d[u], d[v] + 1);
        }
    }
    if (u == 1) {
        int fmax = 0, smax = 0;
        for (auto i: G[u]) {
            if (i != fa) {
                if (d[i] > fmax) {
                    smax = fmax;
                    fmax = d[i];
                } else if (d[i] > smax && d[i] <= fmax)
                    smax = d[i];
            }
        }
        ans = fmax + smax + 2;
    }
}
带权树的直径

权值在点上

例题链接

d ( u ) d(u) d(u)表示以节点 u u u为根,延伸到叶子节点的最长路径的长度,路径可以是节点本身

初始化 d ( u ) = w [ u ] d(u)=w[u] d(u)=w[u]

状态转移: d ( u ) = m a x ( d ( u ) , d ( v ) + w [ u ] ) d(u)=max(d(u),d(v)+w[u]) d(u)=max(d(u),d(v)+w[u])

答案更新:如果叶子节点就是起点,该节点就是终点,那么只需在 d f s dfs dfs的最后更新 a n s = m a x ( a n s , d ( u ) ) ans=max(ans,d(u)) ans=max(ans,d(u));但是也有可能是 u u u节点为中间节点,那么最长的路径应该考虑该根节点下的两个叶子连成的路径 a n s = m a x ( a n s , d ( u ) + d ( v ) ) ans=max(ans,d(u)+d(v)) ans=max(ans,d(u)+d(v))

vector<int> G[maxn];
ll d[maxn], w[maxn];
ll ans = -inf;

void dfs(int u, int fa) {
    d[u] = w[u];
    for (auto v: G[u])
        if (v != fa) {
            dfs(v, u);
            ans = max(ans, d[u] + d[v]);  //注意先更新ans
            d[u] = max(d[u], d[v] + w[u]);
        }
    ans = max(ans, d[u]);
}

权值在边上

例题链接

不难发现权值在边上相当于无权树的边权不为 1 1 1的情况。两次 d f s dfs dfs可以解决非负权树但是无法解决负权树,而树形DP都可以解决

建树采用前向星存边的方式,大体思路和权值在点上一致,只是这里从叶子节点转移而来相当于权值为0

struct node {
    int next, to, w;
} edge[maxn];

int head[maxn], tot;
ll d[maxn], ans = -inf;

void add_edge(int u, int v, int w) {
    tot++;
    edge[tot] = {head[u], v, w};
    head[u] = tot;

    tot++;
    edge[tot] = {head[v], u, w};
    head[v] = tot;
}

void dfs(int u, int fa) {
    d[u] = 0;
    for (int i = head[u]; i; i = edge[i].next){
        int v = edge[i].to, w = edge[i].w;
        if (v == fa) continue;
        dfs(v, u);
        ans = max(ans, d[u] + d[v] + w);
        d[u] = max(d[u], d[v] + w);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值