树型动态规划基础

树型动态规划的题目一般是:给出你一棵树,每个点或者每条边上有对应的权值,并给出一定的限制条件,求取哪些点使得权值最大或最小。

例如说,规定相邻的两个结点不能同时取,如何取才能取到最大值?

我们发现,整个问题可以分解成若干个子问题,即通过求子树的最优值,从而推出整棵树的最优值。

那么,可以采用递归的方式,从根节点开始遍历完所有子树,对于每个结点,有取与不取两种状态。

设dp[i][0]为不取i结点时的最优值,dp[i][1]为取i结点时的最优值,那么:

dp[i][1]=sum(dp[i所有子结点][0])+a[i],其中a[i]为当前点的权值,因当前点取,子节点一概不能取;

dp[i][0]=sum(max(dp[i所有子结点][0],dp[i所有子结点][1])),这时因当前点不取,便不加a[i],且子节点均可选择取或不取(注意不能误认为dp[i][1]一定大于dp[i][0])。

大致的过程是这样:

void treedp(int now) //now为当前结点
{
    for i={now的所有子结点}
    {
        treedp(i); //递归求解子树最优值
        dp[now][0]+=max(dp[i][0],dp[i][1]);
        dp[now][1]+=dp[i][0]; //状态转移方程;
    }
    dp[now][1]+=a[now];

}

以上是边求解子结点边更新,当然也可以求解完再进行状态转移,即:

void treedp(int now)
{
    for i={now的所有子结点} treedp(i);
    for i={now的所有子结点}
    {
            dp[now][0]+=max(dp[i][0],dp[i][1]);
            dp[now][1]+=dp[i][0];
    }
    dp[now][1]+=a[now];
}

例如对于上面这棵树,我们边求解子结点边更新为例。(已经看懂了的可以跳过此部分)

treedp(1),now=1,找到1的子结点2(我们假定先找左子树后找右子树,对结果无影响),treedp(2);

此时now=2,找到2的子结点4,treedp(4);

now=4,4无子结点,于是dp[4][0]=0,dp[4][1]=1;

回到now=2,dp[2][0]+=max(dp[4][0],dp[4][1])=1,dp[2][1]+=dp[4][0]=0,找到2的子结点5,treedp(5);

now=5,5无子结点,于是dp[5][0]=0,dp[5][1]=1;

回到now=2,dp[2][0]+=max(dp[5][0],dp[5][1])=2,dp[2][1]+=dp[5][0]=0,此时2的子结点全部更新完,dp[2][1]+=a[2]=5;

回到now=1,dp[1][0]+=max(dp[2][0],dp[2][1])=5,dp[1][1]+=dp[2][0]=2,找到1的子结点3,treedp(3);

now=3,3无子结点,于是dp[3][0]=0,dp[3][1]=2;

回到now=1,dp[1][0]+=max(dp[3][0],dp[3][1])=7,dp[1][1]+=dp[3][0]=2,此时1的子结点全部更新完,dp[1][1]+=a[1]=5.

最终答案就为max(dp[1][0],dp[1][1])=7。

如果权值不在点上而在边上,也可以将边权设为连接子结点的点权,转化为上述问题求解。

如果有对取得条件有其他更多的限制,则需要定义更多的状态,同时也要设置更多的状态转移方程。

如果有机会,我还会在《进阶篇》介绍更多。

猜你喜欢

转载自blog.csdn.net/weixin_39872717/article/details/80843709