树形DP(经典题目)

做树形dp常用的两种方法:

1.由子节点来更新父节点

2.由父节点来更新子节点

例1.树的直径 (树中最远两点的距离)

思路:对每个点来说,求出以它为父节点,路径的最大值和次大值

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=2e4+10;

int e[N],ne[N],w[N],h[N],idx;
int f1[N],f2[N];//f1[i]表示父节点为i的最远距离,f2[i]表示次远距离

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

void dfs(int u,int fa)
{
    //f1[u]=0,f2[u]=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs(j,u);
        int d=f1[j]+w[i];
        if(d>f1[u]){
            f2[u]=f1[u];
            f1[u]=d;
        }
        else if(d>f2[u]){
            f2[u]=d;
        }
    }
}
signed main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs(1,-1);
    
    int ans=-1e9;
    for(int i=1;i<=n;i++){
        ans=max(ans,f1[i]+f2[i]);
    }
    cout<<ans<<endl;
    return 0;
}

例2:

树的中心(以某个点为中心,其到其他点的最大值最小,则该点为中心)

活动 - AcWing

思路:

我们可以像例1一样,例1是以1为根,来找最长路径,我们可以把每个点都当作一次根,都做一次遍历,来求最长路径,然后取min即是答案,但这样会超时,因此考虑换个思路。

1.对每个节点,其到其他点最大值为max(向下到其儿子最远点,向上的最远点),这里前一个很好求,即例1中的f1[i], 而对第二个要进行讨论。

2.对于向上的最远点有两种情况:向上走就是求一个点的父节点的不走该节点的最长路径,其实我们知道了每一个节点向下走的长度就可以知道向上的最长路径了,一个子节点 j 的向上最长路径就是 它的父节点 u 的最长向上路径和最长向下路径取最大值,如果向下最长路径经过了 j 就改为第二长的向下路径,对应代码:

if(p1[u]==j)up[j]=max(up[u],d2[u])+w[i];
else up[j]=max(up[u],d1[u])+w[i];

因此做两次树形DP即可

代码:

​
#include<bits/stdc++.h>

using namespace std;

const int N=2e4+10;

int e[N],ne[N],h[N],w[N],idx;

int f1[N],p1[N],f2[N];//p[i]记录最长路径是由哪个子节点转移过来的
int up[N];//向上走的最大值

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

void dfs1(int u,int fa)
{
    f1[u]=f2[u]=0;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs1(j,u);
        int d=f1[j]+w[i];
        if(d>f1[u]){
            f2[u]=f1[u];
            f1[u]=d;
            p1[u]=j;//记录最长路径由哪个儿子转移来的
        }
        else if(d>f2[u]) f2[u]=d;
    }
}

void dfs2(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        if(p1[u]==j)//说明向上走的最大路径经过该儿子节点
        {
            up[j]=max(up[u],f2[u])+w[i];
        }
        else up[j]=max(up[u],f1[u])+w[i];

        dfs2(j,u);//注意这里是由父节点更新子节点
                  //因此要先更新信息在递归处理,与dfs1不同
    }
}
signed main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);

    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs1(1,-1);//由子节点更新父节点
    dfs2(1,-1);//由父节点更新子节点
    int ans=1e9;
    for(int i=1;i<=n;i++){
        ans=min(ans,max(f1[i],up[i]));
    }
    cout<<ans<<endl;
    return 0;
}


​

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值