树形dp笔记

树形dp

换根dp

基本思想

做两遍dfs

1.用dfs来从下到上来维护子树的贡献

2.用另一个dfs来从上到下维护父节点对子节点贡献

模版:ACWing 1073 树的重心

[题目链接](1073. 树的中心 - AcWing题库)

思路
  1. 暴力求解就是以每一个节点分别为根,dfs一遍求到其他节点的距离

  2. 节点到其他节点的最大长度是一个节点向上搜和向下搜的最大值

  3. 维护子树的最大长度就是维护一个次大值和一个最大值,维护最大值是那个子树贡献的(后续会提到)

  4. 维护v节点向上的贡献,父节点u到子节点v所组成的边权为w,up[v] = w[i] + ……, 而省略号中的部分就是父节点u所带来的贡献(父节点的最大距离),这里有一个需要注意的点是有可能父节点的最大距离是经过v的这个时候就不能选取最大值了,要改选次大值

注意:次大值是从子节点向父节点搜的时候父节点的子树的次大值

  1. 最后遍历一遍节点即可
代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1E4 + 10,M = 2 * N, inf = 0x3f3f3f3f;

int n,m;
int h[N], e[M], ne[M], w[M], idx;
int up[N],d1[N],d2[N];
bool is_leaf[N];//特判叶子节点
int p[N];

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

int dfs_down(int u,int fa)
{
    d1[u] = d2[u] = -inf;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        
        int d = dfs_down(j ,u) + w[i];
        
        if(d >= d1[u])
        {
            d2[u] = d1[u]; d1[u] = d;
            p[u] = j;
        }
        else if(d > d2[u]) d2[u] = d;
    }
    
    if(d1[u] == -inf)
    {
        d1[u] = d2[u] = 0;
        is_leaf[u] = true;
    }
    
    return d1[u];
}

void dfs_up(int u, int fa)
{
    for (int i = h[u]; i != -1 ; i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        
        if(j == p[u]) up[j] = max(up[u], d2[u]) + w[i];
        else up[j] = max(up[u], d1[u]) + w[i];
        
        dfs_up(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_down(1,0);
    
    dfs_up(1,0);
    
    int ans = inf;
    for (int i = 1; i <= n ; i ++) 
    {
        if(is_leaf[i]) ans = min(ans, up[i]);
        else ans = min(ans, max(up[i], d1[i]));
    }
    
    cout << ans << endl;
    
    return 0;
}

例题 ACWing 287 积蓄程度

题目链接

思路

基本和上面的题一样,只不过维护的的是可以流通的水量这里有一个注意的点就是在递归到u–v的时候这个河道的可以流过的水量数不仅仅和子树的有关还和这个河道的通过流量有关,两者需要求一个最小值,其中入海口不用考虑直接相加,第二遍的时候也是同理

AC code
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 2E5 + 10, M = N * 2;

int n;
int h[N],e[M], ne[M], idx;
ll w[M];
ll d[N],f[N];
int du[N];

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

int dfs_down(int u, int fa)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        dfs_down(j,u);
        if(du[j] == 1) d[u] += w[i];
        else d[u] += min(d[j], w[i]);
    }
}

void dfs_up(int u, int fa)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        
        if(du[u] == 1) f[j] = d[j] + w[i];//父节点是叶子结点
        else if(du[j] == 1) f[j] = d[j] + min(f[u] - w[i], w[i]);//子节点是叶子节点
        else f[j] = d[j] + min(f[u] - min(d[j],w[i]), w[i]);//一般情况
        dfs_up(j,u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n ; i ++)
    {
        h[i] = -1;f[i] = d[i] = du[i] = 0;
    }
    for (int i = 0; i < n - 1; i ++)
    {
        int a,b,c;cin >> a >> b >> c;
        add(a,b,c), add(b,a,c);
        du[a] ++, du[b] ++;
    }
    
    dfs_down(1, -1);
    f[1] = d[1];
    dfs_up(1, -1);
    
    ll ans = 0;
    for (int i = 1 ;i <= n; i ++) ans = max(ans, f[i]);
    
    cout << ans << endl;
}

int main()
{
    int t;
    cin >> t;
    while(t --) solve();
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值