dp之树形dp

树形dp:
需要解决的问题,他的数据结构是树

acwing 树的最长路径
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。

现在请你找到树中的一条最长路径。

换句话说,要找到一条路径,使得使得路径两端的点的距离最远。

注意:路径中可以只包含一个点。

输入格式
第一行包含整数 n。

接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。

输出格式
输出一个整数,表示树的最长路径的长度。

思路:
树形dp,一般是由子节点来推出父节点

题中要求的是,所有路径中的最大路径

那么可以把经过父节点的所有路径归到父节点上

现在求经过父节点的所有路径中的最大

1.最大路径是一条到子节点的路径
2.最大路径是从一个子节点经过父节点到另一个子节点的路径

解决方法,保存所有值,保存最大值和次小值,由这2个值就可以求出第二种情况
因为有负的,不过题中说了一个点也可以,所以最小值是0,故答案一开始赋值为0

#include <cstring>
#include <iostream>

using namespace std;

const int N = 200010;
int h[N],ne[N],val[N],w[N],idx;
int res = 0;

void add(int a,int b,int c)
{
    val[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx ++;
}

int dfs(int u,int fa)
{
    int d1,d2;
    d1 = d2 = 0;
    for(int i = h[u];~ i;i = ne[i])
    {
        int j = val[i];
        if(j == fa) continue;
        int d = dfs(j,u) + w[i];
        if(d >= d1) d2 = d1,d1 = d;
        else if(d > d2) d2 = d;
    }
    
    res = max(res,d1 + d2);
    return d1;
    
}

int main()
{
    int n;
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i = 1;i < n;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
        add(b,a,c);
    }
    
    dfs(1,-1);
    
    cout << res << endl;
    
    return 0;
}

acwing 树的中心

给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。

请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。

输入格式
第一行包含整数 n。

接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。

输出格式
输出一个整数,表示所求点到树中其他结点的最远距离。

思路:

题中要求出,一个点,他到离他最远的点的距离最小

也就先要求出来所有点到离它最远的点的距离,然后取个最小值

因为树形dp,一般是子节点更新父节点
所以,可以把每一个节点看成一个父节点,求出由父节点开始的所有路径中最大值

首先求出往子节点走的最大值,也就是上题的做法

然后考虑往父节点的父节点走的方法

2种情况:
父节点的父节点他往下走的最大值就是走到这个点来的
那么它要往上走的话,就可以往它父节点的次大值那边走,或者继续往上走

父节点的父节点往下走的最大值不是走到这个点来的
那么它就可以往最大值那边走,或者继续往上走

最后求出每个点往下和往上的最大值,再取个最小值

#include <iostream>
#include <cstring>

using namespace std;

const int N = 20010;
int ne[N],h[N],val[N],w[N],idx;
int n;
int d1[N],d2[N],up[N],p1[N],p2[N];

void add(int a,int b,int c)
{
    val[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx ++;
}

int dfs(int u,int fa)
{
    for(int i = h[u];~ i;i = ne[i])
    {
        int j = val[i];
        if(j == fa) continue;
        int d = dfs(j,u) + w[i];
        if(d >= d1[u]) d2[u] = d1[u],d1[u] = d,p1[u] = j;
        else if(d > d2[u]) d2[u] = d,p2[u] = j;
    }
    return d1[u];
}

void f(int u,int fa)
{
    for(int i = h[u];~ i;i = ne[i])
    {
        int j = val[i];
        if(j == fa) continue;
        if(p1[u] == j) up[j] = max(d2[u],up[u]) + w[i];
        else up[j] = max(d1[u],up[u]) + w[i];
        f(j,u);
    }
}

int main()
{
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i = 1;i < n;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
        add(b,a,c);
    }
    
    dfs(1,-1);
    
    f(1,-1);
    
    int res = 0x3f3f3f3f;
    for(int i = 1;i <= n;i ++) res = min(res,max(d1[i],up[i]));
    
    cout << res << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值