进阶算法——树形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]);
}

 

posted @ 2019-08-30 11:40  L1ngYi  阅读(284)  评论(0编辑  收藏  举报