树形背包

前言:1 树形动归

树形动归就是在树上的动归,树形动归一般是依赖于dfs的,根据动归的后效性,父节点的状态一般都依赖子节点的状态以某种方式转移而来,而每一个父节点的孩子的数量不定,这就很难以寻常的递推式通过几个for解决掉,而树这种东西它的遍历本身就依赖于dfs,可以说是比较暴力的打法了!

差不多是这个样子的:

先存图,以dfs进去找到叶节点,先处理叶节点的信息再一层层向父节点推进,根据题意进行选择列出转移方程

void dfs(int nx){
    f[nx][0]=0;
    f[nx][1]=val[nx];
    for(int i=0;i<son[nx].size();i++){
        int ny=son[nx][i];
        dfs(ny);
        f[nx][0]+=max(f[ny][0],f[ny][1]);
        f[nx][1]+=f[ny][0];
    }
}

 

这里是一道树形DP的经典题,新手最好先打打这个

没有上司的舞会 https://www.luogu.org/problemnew/show/P1352

前言:2 树形背包

树形背包就是原始的树上动归+背包,一般用来处理以一棵树中选多少点为扩展的一类题,基本做法与树上dp无异,不过在状态转移方程中会用到背包的思想。

它基本上是这个样子的:

存图(看个人喜好吧,我比较习惯邻接表),然后dfs进去补全子节点的信息,f数组的意思是以fa为中转点,找出fa往下的可取1~j个点时各自的最大收益。

void dfs(int fa){
    for(int i=0;i<son[fa].size();i++){
        int ny=son[fa][i];
        dfs(ny);
        for(int j=m+1;j>=1;j--){
            for(int k=j-1;k>=1;k--){
                f[fa][j]=max(f[fa][j],f[fa][j-k]+f[ny][k]);
            }
        } 
    }
}

 

这道题与洛谷p2014除了细节的处理外大体思路一样,都可以算是树形背包的板子题了,这里给出p2014的链接

选课 https://www.luogu.org/problemnew/show/P2014

二叉苹果树

链接:https://www.luogu.org/problemnew/show/P2015

做题的思路应该是首先从出题人千奇百怪的题目描述中抽离出它的本质, 然后我们就发现了

1这是个二叉树

2 n和q的范围不大

当然这都是废话

分析题意:从树根往下找q条边使得最后得到的苹果数最多,最后得到的还得是个完整的树

思路相同:从叶节点向上翻,每一个点找出以它为根节点的1~q的最大利益,除叶节点外,因为父节点不可能只从一个子节点那里要q-1条边(连接父、子节点还有一条边),所以就用到了背包的思想。其实有种随机搭配的感觉,父节点的大儿子要m条边,小儿子要w条边,自己留q-m-w-1条用来连接根节点求最大。

val保存子、父边的苹果数,所以f[x][j-k-1]中,父节点自己留的可用边-1。

 for(int j=q;j>=1;j--){
            for(int k=j-1;k>=0;k--){
                f[x][j]=max(f[x][j],val[x][ny]+f[ny][k]+f[x][j-k-1]);
            }
        }

 

这里有几个点需要注意:

1这里注意苹果是长到边上的,但我们存的f数组是存的点。这里可以把边上的苹果直接给这条边连接的子节点,因为一条边连2个点,如果取则两个点都要,不取子节点一定不要但父节点还有可能作为别人的子点被用。

2输入的边子、父关系未给,需要双向存但遍历时要特判。(苹果数也要存两遍)

3还有就是刚才所说的父节点-1的问题了

给一下代码

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

vector <int> son[109];
int  n,q;
int f[109][109],val[109][109],used[109];

void dfs(int x){
    used[x]=1;//防止死循环做的标记       
    for(int i=0;i<son[x].size();i++){
        int ny=son[x][i];
        if(used[ny]==1) continue;//如果标记过则代表这是它的父节点,直接跳过
        used[ny]=1;
        dfs(ny);
        for(int j=q;j>=1;j--){
            for(int k=j-1;k>=0;k--){
                f[x][j]=max(f[x][j],val[x][ny]+f[ny][k]+f[x][j-k-1]);
            }
        }
    }
}

int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        val[a][b]=c;//因为不知道关系存的两次价值(但只会用到一个)
        val[b][a]=c;
        son[a].push_back(b);
        son[b].push_back(a);
    }
    dfs(1);
    printf("%d",f[1][q]);
    return 0;
}

 

posted @ 2019-06-14 17:53  sdzmq  阅读(691)  评论(0编辑  收藏  举报