[ACM学习]自上而下树形dp

问题引入

设置dp状态,相比于更容易出错的贪心更...不易出错。

状态设计

如果选择父结点,就会使孩子结点不能被选择,我们会多开一维的dp,用来标记该点是否被标记过。

以1点举例,f[1][0]为不选它的状态,那么它的子结点2 3 是可选可不选的,所以是 max(f[2][0],f[2][1])+max(f[3][0]+f[3][1]) ,在子结点的两个状态里挑最大值,并且子结点间没有限制,所以直接相加。

f[1][1]=f[2][0]+f[3][0]

所以这题的总体思路是:从叶子结点开始,dp[v][0]=0 dp[v][1]=a_v,并且到达根。

最后结果在 dp[root][0] dp[root][0]里挑一个max

void dfs(int u, int fa)
{
    for (int i = head[u]; i; i = edge[i].nex)
    {
        int v = edge[i].to;
        if (v != fa)
            continue;
        dfs(v, u);
        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0] ;
    }
    f[u][1] += a[u];
    return ;
}

在这里解释一下自上而上:父结点的状态转移方程是会受到孩子状态值的影响。算法进行的方向还是从叶子计算到根。

问题二

状态设计

二维可以解决,将是否选择这个点并入体积里(选了这个点即为多这个点的体积)

状态转移是什么样的?可以把一个子树的几个孩子看成是几个物品,用类似于背包问题进行状态转移。

当前结点u在体积v1下,由体积v1-v2下加上某一孩子的v2体积下价值与它原来的值进行大小对比。所以我们会对v1 v2进行枚举。

虽然我们类比了01背包,但是和01背包问题相比,某一个子树的体积不是某个定值,而是会从0到最大限度进行枚举。

void dfs(int u, int fa)
{
    memset(f[u], -0x3f, sizeof f[u]);
    if (v[u] <= V)
        f[u][v[u]] = w[u];  //把当前结点放进去,以u为根的子树里,还只放入了结点u
    for (int i = head[u]; i; i = edge[i].nex)   //枚举每一个子树
    {
        int v = edge[i].to;
        if (v == fa)
            continue;
        dfs(v, u);  
   //从叶子开始,
    //,每个孩子结点看成不同的物品,进行01背包
        vector<int> nf(f[u], f[u] + V + 1);  //当前子树的背包过程,用来转移
        for (int v1 = 0; v1 <= V; v1 ++)   //含义:在放入这个子树前,已使用的体积
        {
            for (int v2 = 0; v1 + v2 <= V; v2 ++ )  //放入v2后不能超过V
            {
                nf[v1 + v2] = max(nf[v1 + v2], f[u][v1] + f[v][v2]);
            }
        }
        for (int v = 0; v <= V; v ++ )
            f[u][v] = nf[v];
    }
    return ;
}

问题二进阶

siz[u] 是指,把包含u在内,u为根的子树,把所有权值都加上,得到的和。

虽然还是两层for,但是如果在每个结点都体积为1的情况下,相当于是把以u为子树的每个结点两两进行比较。

最小点覆盖

解决这个问题很清晰,要所有边都被覆盖,每边上至少有一个端点被选择。

状态设计

dp[v][0] 和 dp[v][1] 分别是点v两个状态下的,从叶子到v的权值和最小。

代码

void dfs(int u,int fa)
{
    for (auto v:e[u])
    {
        if (v==fa) continue;
        dfs(v,u);
        dp[u][0]+=dp[v][1];
        dp[u][1]+=min(dp[v][0],dp[v][1]);
    }
    dp[u][1]+=1;
}

最小支配集

状态转移

所以???

分成了三种状态:

  • 选u,且u为根的子树全被支配(包括u)
  • 不选u,且u为根的子树全被支配(包括u)--- 这个最有难度了,如何让u最小代价的孩子被选中。
  • 不选u,且u为根的子树(不包括u)其它点全被支配 --- 为什么有这个选项呢,因为如果这个点的父结点选择的状态是被选了,那么就可以允许现在这个正在计算的状态的存在,并纳入计算。

现来来解释一下那个最有难度的:至少有一个孩子是以 dp[v][1] 且是最小权值的那个孩子被选中

先看后半部分,是在每个子结点 v 里 dp[v][0]和dp[v][1] 里挑一个最小的;

如果全部是dp[v][1]加进去了怎么办?

看看前半部分,先看 min 里面的,按上述情况的发生,里面的min计算后的结果是:dp[v][0]-dp[v][1],经过前面的 min洗礼,会挑个最小的差值来,有了这个会有什么变化呢?

与后面的求和相加,会把v对应的 dp[v][1] 减去,并且加上 dp[v][0],这样就保证了至少有一个孩子被选中

如果有一个dp[v][0]在与dp[v][1]进行min里胜出,那么,它在前半部分的第一个括号里,就是dp[v][0]-dp[v][0]=0,前半部分就是0了。

代码

void dfs(int u)
{
    long long minn=1e18;
    for (auto v:e[u])
    {
        dfs(v);
        dp[u][0]+=min({dp[v][0],dp[v][1],dp[v][2]});
        dp[u][1]+=min(dp[v][0],dp[v][1]);
        minn=min(minn,dp[v][0]-min(dp[v][0],dp[v][1]));
        dp[u][2]+=dp[v][1];
    }
    dp[u][0]+=a[u];
    dp[u][1]+=minn;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值