树形DP初探

来源:互联网 发布:350淘宝装修安全吗 编辑:程序博客网 时间:2024/05/30 04:11

声明:

一下学习内容摘自–树型动态规划 JSOI2010冬令营


关于DP:

DP的赘述就不多说了,强调两点:
最优子结构:一个最优化策略的子策略总是最优的。
无后效性:当前决策与过去状态无关。


再加上树:

有n个点,n-1条边的无向图,任意两顶点间可达
无向图中任意两个点间有且只有一条路
一个点至多有一个前趋,但可以有多个后继
无向图中没有环


引例、问题描述

给定一棵树,树的每个结点有一个权值,要求从中选出一些不相邻的点,使选出的结点权值和最大。

引例、问题分析

首先要给这棵树选一个根,明确了父子关系才有动规的顺序。本题没有特殊要求,只要任意选择一个点作根就可以了。

引例、确定状态

用f[i][0]表示不选i时,以i为根子树的最大权值;用f[i][1]表示选择i时,以i为根子树的最大值。

引例、状态转移

f[i][0]=sum(max(f[j][0],f[j][1]))
f[i][1]=sum(f[j][0])+v[i]

引例、两种实现方式

记忆划搜索:易于实现,但可能会爆栈
拓扑排序+动规:实现起来比较麻烦

引例、两种实现方式

实现方式的选择因题而异,对于本题,首先要保证程序不会出错。但一般来说,在保证正确的前提下,记忆划搜索更加易于实现,且在对于复杂的题目,记忆划搜索更加直观,便于思考。

引例、小结

动态规划都需要一个决策序列,而许多题目的树是无序给出的,要做动规,我们要选择好合适的根。
对于大多数树型动态规划问题,都是用一棵子树的根结点编号来作为代表这棵子树的第一维状态,然后再根据需要加维。
因为树的特殊结构,任何两个点只有唯一通路,所以很容易满足无后效性。假如本题给定的是图而不是树,那么显然就无法用动规解决了。


对于此题代码…觉得贴出来实在是亵渎原作者的原意,个人也觉得不应该看代码,而进行自己打,无非就是一个递推套了个DFS嘛。
赠送案例:

输入:61 2 1 2 4 11 21 31 62 55 4输出:8

感觉到了原文中的第二题深深的恶意,所以还是先写第三题吧

例三 问题描述(ural1018)

有一棵苹果树,如果树枝有分叉,一定是分 2 叉(就是说没有只有 1 个儿子的结点)。 这棵树共有 N(1<=N<=100) 个结点(叶子点或者树枝分叉点),编号为 1-N, 树根编号一定是 1。
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。 给定需要保留的树枝数量P,求出最多能留住多少苹果。

例三、问题分析

这题的权值在边上,这在思考时有些别扭,其实只要把边的权值转移到儿子结点上,问题性质不变。
//其实就是按照树+DP的特性,父节点与子节点的关系联系于状态转移
这样状态就应该容易想到了,dp[i][j]表示以i结点为根的子树保留j个结点所得的最大值。因为根结点没有权值,所以我们要保留p+1个点。
/*******/
//定义:
//dp[i][j]表示以i结点为根的子树保留j个结点所得的最大值。
/*******/
//状态转移:
//对于根节点为 i 保留 j 个节点的子树,枚举左子树保留的节点个数k,
//dp[i][j]=max{dp[i_left][k]+dp[i_right][j-k-1],k [0, j-1] };
//对于边界 dp[i][0] = 0; dp[i][1] = value[i];

具体代码实现:
先按照边输入,然后预处理每个节点为根时当前子树的节点数量。
然后记忆化搜索,对当前的节点 i ,当前保留的节点数 j ,
先判断掉节点是否超过当前节点的范围。
然后对它的子节点枚举子节点数进行DFS。

const int N=1e2+10;const int M=2e2+10;struct Edge{    int val;    int v;    int next;}edge[M];int head[N],tol,n,p;int cnt[N];int w[N];void init(){    tol=0;    memset(head,-1,sizeof(head));}void add(int u,int v,int val){    edge[tol].val=val;    edge[tol].v=v;    edge[tol].next=head[u];    head[u]=tol++;}bool vis[N];int dp[N][N];int DFS(int u,int num,int pre){    int v,temp;    if(num>cnt[u]) return 0;    if(dp[u][num]!=-1) return dp[u][num];    int node1=-1,node2=-1;    for(int i=head[u];~i;i=edge[i].next){        v = edge[i].v;        if(v==pre) continue;        if(node1 == -1)            node1 = v;        else            node2 = v;    }    temp = 0;    for(int i=0;i<num;i++){        temp = max(DFS(node1,i,u)+DFS(node2,num-1-i,u),temp);    }    dp[u][num] = temp + w[u];    return dp[u][num];}void solve(){    memset(vis,false,sizeof(vis));    memset(dp,-1,sizeof(dp));    for(int i=1;i<=n;i++){        dp[i][0] = 0;        dp[i][1] = w[i];    }    vis[1] = true;    printf("%d\n",DFS(1,p+1,1));}int DFS1(int u){    int v,res;    if(cnt[u]) return cnt[u];    res = 1;    for(int i=head[u];~i;i=edge[i].next){        v = edge[i].v;        if(vis[v]) continue;        vis[v] = true;        w[v] = edge[i].val;        res += DFS1(v);    }    cnt[u] = res;    return  res;}int main(){    int u,v,val;    scanf("%d%d",&n,&p);    init();    for(int i=1;i<n;i++){        scanf("%d%d%d",&u,&v,&val);        add(u,v,val);        add(v,u,val);    }    memset(vis,false,sizeof(vis));    vis[1] = true;    memset(cnt,0,sizeof(cnt));    DFS1(1);    solve();    return 0;}
0 0
原创粉丝点击