一、概念  

  顾名思义,树型动态规划就是在“树”的数据结构上的动态规划,平时作的动态规划都是线性的或者是建立在图上的,线性的动态规划有二种方向既向前和向后,相应的线性的动态规划有二种方法既顺推与逆推,而树型动态规划是建立在树上的,所以也相应的有二个方向: 

    1、叶->根:在回溯的时候从叶子节点往上更新信息

    2、根 - >叶:往往是在从叶往根dfs一遍之后(相当于预处理),再重新往下获取最后的答案。

    不管是 从叶->根 还是 从 根 - >叶,两者都是根据需要采用,没有好坏高低之分。

二、优点

  树本身至少就有“子结构”性质(树和子树);也本身就具有递归性。所以在树上DP其实是其所当然的事,相比线性动态规划来讲,转移方程更直观,更易理解。

三、难点

  1、和线性动态规划相比,树形DP往往是要利用递归的,所以对递归不熟悉的同学,是一道小小的障碍,说是小小的,因为要去理解也不难。

   2、细节多,较为复杂的树形DP,从子树,从父亲,从兄弟……已经一些小的要处理的地方,脑子不清晰的时候做起来颇为恶心
   3、状态表示和转移方程,也是真正难的地方。做到后面,树形DP的老套路都也就那么多,难的还是怎么能想出转移方程,状压DP、概率DP这些特别的DP应该说做到最后都是这样!
 
前置知识:边表
//一、边表(链式前向星) 
struct edge
{
    int e;//e为一条边的终点 
    int next;//next为同一父节点的下一条边
            //例如,3和2是1的子节点且3在2前面
            //那么3.next=2 
}ed[maxn];

int first[maxn];
//first[n]是父节点为n的第一条边 

//加边: 
void add_edge(int s,int e)//添加一条从s到e的边 
{
    en++;//新加入的边 
    ed[en].next=first[s];
    //表示:现在新加的这一条边,当做从s开始的第一条边
    //类似一个倒序输入,越晚加的边序号越小 
    first[s]=en;
    //由上述分析可知:那么父节点为s的第一条边就是en了 
    ed[en].e=e;    
    //这一条边的终点就是e 
} 

//遍历:
for(int p=first[s];p!=0;p=ed[p].next)
//p从s的第一条子边开始循环,每次变成相同父节点(s)的下一条边 
//直到为0时结束循环 

最简单的树形DP:给定一棵N个点的树,用树形DP的方法求出该棵树的节点数目

设置状态:f[i]为以i为根的子树,此时表示以i为根的子树有多少节点 
void dfs(int now,int fa)
{
    //fa为now的父节点 
    f[now]=1;
    for(int p=first[now];p!=0;p=ed[p].next)
        if(ed[p].next!=fa)
        {
            //如果这条边不是指向它父亲的话 
            dfs(ed[p].e,now);
            //就dfs,把now当作父节点 
            f[now]+=f[ed[p].e];
            //此时ed[p].e是now的一个子节点 
        }
}

 

posted on 2020-04-22 21:52  Allen_lml  阅读(212)  评论(0编辑  收藏  举报