• 进阶算法——树形DP


    特点(特别之处)

    DP这种东西…跟玄学一样…除非讲题,不然是真的没啥东西总结…

    通过少量局部不完全不具代表性的刷题得出…

    树形DP一般是在递归环境进行的,并且是先递到底

    树形DP的第一维状态往往是以i为节点的子树。

    题目中的ShowTime

    因为动态规划的恐怖之处…所以还是多通过题目感受吧…

    选课

     首先很容易看出来,这题的数据是二叉树。

    对于某一个非根节点来说,如果你想选择这个节点,那么就必须选择他的父节点。

    那么对于一个节点,如果左儿子选了y个节点,右儿子就一定是要选y-1-k个节点(因为父节点要选,需要扣除一个节点位)

    这样就可以推出状态,F[x][y]表示以x为根节点的子树,选择y个节点的最大值

    那么就可以得到方程

    F[x][y]=max(f[left[x][k]+f[right[x]]][y-1-k]+v[x])

    枚举每个节点的左右儿子,循环k的值即可(0——y-1)

     选课2.0

     观察一下可以发现,这题又上道题数据是二叉树,变成了数据是森林。

    我们如何处理森林的数据呢?首先将森林转换成一棵树。也就是说,对于全部的树,建立一个虚拟的根节点,让所有森林树的根连接虚拟根节点,让虚拟根节点成为“虚拟根”。这样,森林也就变成了一颗庞大的树。

    如何让庞大的树变成二叉树?这里要引入一个口诀概念“左儿子右父亲”

    也就是说,让这棵树的所有节点的左起第一个孩子变成他的左孩子,右起第一个兄弟变成右孩子。

     那么如何实现这个操作呢?实际上只有两行代码

     

    传送门 portal JZOJ 5906

     

    Input
    5
    1 2 2
    2 3 3
    2 4 5
    1 5 1
    OutPut
    13

    惋惜的是,这道题当时在考试的时候错误的理解成了搜索之类的或者树生成。

    看了题之后觉得有点昏,因为对于在哪里开门、开哪个门、如何判重等等十分疑惑。

    但没想到是个树形Dp。

    认真思考之后,确实是有如下规律:

    1.我们不可能在某一子树尚未遍历完的情况下用传送门跑到别的子树

    2.对于每个点的状态,无非只有开启传送门或不开启传送门

    3.每条边都至少会被走一次,在没有门的情况下会走两次。至多情况下只走两次。

    那么就可以设计一个方程式。以F[x]表示以x为根节点的子树遍历完返回所需要的最小时间。

    那么 ,对于x的两种决策:

    1:开启传送门,其子树中最长的那条只算一次(因为返回用门,无需二次遍历),其他的都算两次。也就是,设定y为子节点,len为最长条的长度,wealth表示根x到节点y的距离。该状态产生的结果为  Sum[y]*2-len[y]+wealth

    2:不开启传送门,那么其所有子树下的所有边都将被遍历2次,也就是,设定y为x的子节点,以y为根节点的子树遍历完回到y的时间,加上x到y所需要的代价*2(往一次 ,返一次),便得到状态F[y]+wealth*2

    对两种决策取较小者,要注意F[x]在遍历中是累加的,因为要遍历每一个x下的子节点y才能产生答案。

    下面是代码。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    const int MAXN=1000010;
    struct Edge{
        int Wealth;
        int End;
        int Next;
    }Edges[2000020];
    int Head[2000020];
    int idx;
    void Add(int start,int end,int value){
        Edges[++idx].End=end;
        Edges[idx].Wealth=value;
        Edges[idx].Next=Head[start];
        Head[start]=idx;
    }
    long long F[MAXN];
    long long Length[MAXN];
    long long Sum[MAXN]; 
    int a,b,c;
    int n;
    void Dfs(int x,int fa){
        for(int i=Head[x];i;i=Edges[i].Next){
            if(Edges[i].End!=fa){
                Dfs(Edges[i].End,x);
                Length[x]=max(Length[x],Length[Edges[i].End]+Edges[i].Wealth);
                Sum[x]+=Sum[Edges[i].End]+Edges[i].Wealth;
                F[x]+=min(F[Edges[i].End]+Edges[i].Wealth*2,Sum[Edges[i].End]*2-Length[Edges[i].End]+Edges[i].Wealth);
                
            }
        }
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&a,&b,&c);
            Add(a,b,c);
            Add(b,a,c);
        }
        Dfs(1,0);
        printf("%lld",F[1]);
    }
  • 相关阅读:
    (转)UML类图关系(泛化 、继承、实现、依赖、关联、聚合、组合)
    (转)使用 Document!X 为自己的dll生成一个漂亮的说明文档
    我的游戏学习日志42——游戏机制设计(2)
    我的游戏学习日志41——游戏机制设计(1)
    我的游戏学习日志40——游戏概念设计(2)
    我的游戏学习日志39——游戏概念设计(1)
    我的游戏学习日志38——游戏性的评估标准(2)
    我的游戏学习日志37——游戏性的评估标准(1)
    我的游戏学习日志36——游戏结构(小结)
    我的游戏学习日志35——游戏结构(4)
  • 原文地址:https://www.cnblogs.com/Uninstalllingyi/p/11434166.html
Copyright © 2020-2023  润新知