【算法详解】基础树形动态规划

关于树形DP我们学过的一般的DP方程都是一维或者二维的,而数组是一种线性结构,具有很强的位置关系限制(一个挨着一个),所以一般在线性结构上的DP都很容易计算。然而除了线性的数据结构之外,还有很多非线性的数据结构,最典型的就是树。由于树本身的特殊性质,树的局部性体现在其递归结构上:即以一个节点为根的子树是由它儿子们的子树组成的,所以我们在解决树形DP问题的时候一般是选择按照层数从底向上,也就是...
摘要由CSDN通过智能技术生成

关于树形DP

我们学过的一般的DP方程都是一维或者二维的,而数组是一种线性结构,具有很强的位置关系限制(一个挨着一个),所以一般在线性结构上的DP都很容易计算。

然而除了线性的数据结构之外,还有很多非线性的数据结构,最典型的就是树。

由于树本身的特殊性质,树的局部性体现在其递归结构上:即以一个节点为根的子树是由它儿子们的子树组成的,所以我们在解决树形DP问题的时候一般是选择按照层数从底向上,也就是对于每个节点,用其子节点的子树答案计算出它的子树答案。

而树形DP一般需要使用递归的形式解决。
接下来,我们就从几道简单易懂的例题中去分析树形动态规划的做法


1.战略游戏

题目描述

Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。

请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵。

Solution
对于这道题,因为由n个点n-1条边组成,所以我们要使用树形DP。

而树形DP的主要思路就是:选取一个根节点,再扩展子节点,得到子节点的DP值,根据子节点的DP结果通过回溯获得DP值。

我们不妨设第一个点为根节点。设f[i][0/1]表示以第i个节点为根节点,第i个节点是否有是(1)否(0)有士兵,使整棵树都满足题意的最小代价。

则状态转移方程十分简单:
f [ i ] [ 0 ] = ∑ j ∈ s o n ( i ) f [ j ] [ 1 ] f[i][0]=\sum_{j\in son(i)} f[j][1] f[i][0]=json(i)f[j][1]
f [ i ] [ 1 ] = ∑ j ∈ s o n ( i ) m i n ( f [ j ] [ 0 ] , f [ j ] [ 1 ] ) + 1 f[i][1]=\sum_{j\in son(i)} min(f[j][0],f[j][1])+1 f[i][1]=json(i)min(f[j][0],f[j][1])+1

而主要的难点则是枚举的过程:

void dp(int x)
{
   
	v[x]=1;
	标记,去重,避免子节点和父亲节点相互枚举
	for (int i=0;i<a[x].size();++i)
	{
   
		int y=a[x][i];
		if (v[y]) continue;
		避免往回搜索 
		dp(y);
		对子节点进行状态转移
		f[x][0]+=f[y][1];
		f[x][1]+=min(f[y][0],f[y][1]);
		进行状态转移
	}
	f[x][1]++;
	不要忘了加上自身
	return;
}

需要通过深搜,一个特殊的枚举方式来枚举树形结构;在对于节点i进行状态转移之前,由于需要子节点的状态,因此在状态转移之前需要对子节点也进行一次DP转移;通过子节点转移以后,利用回溯将子节点的DP值完成转移,再转移回上一层。

仔细理解一下其独特的枚举方式。

参考代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1600;
int f[N][2],n,sum=0,v[N];
vector < int > a [N] ;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值