Bootstrap

「总结」树形DP

什么是树形DP?

概念:

给定一棵有N个节点的树(通常是无根树,也就是有N-1条无向边),我们可以任选一个节点为根节点,从而定义出每个节点的深度和每棵子树的根。

在树上设计动态规划算法时,一般就以节点从深到浅(子树从小到大)的顺序作为DP的“阶段”。DP的状态表示中,第一维通常是节点编号(代表以该节点为根的子树)。大多数时候,我们采用递归的方式实现树形动态规划。对于每个节点x,先递归在它的每个子节点上进行DP,在回溯时,从子节点向节点x进行状态转移。

如何树形DP?

树形DP一般可以解决三类问题:
①最大独立子集
②树的重心
③树的直径
而这三类问题是树形dp题的基础。

最大独立子集

最大独立子集的定义是,对于一个树形结构,所有的孩子和他们的父亲存在排斥,也就是如果选取了某个节点,那么会导致不能选取这个节点的所有孩子节点。一般询问是要求给出当前这颗树的最大独立子集的大小(被选择的节点个数)。

最经典的莫过于这道题:

没有上司的晚会

题目描述
Ural大学有N个职员,编号为1~N。他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。每个职员有一个快乐指数。现在有个周年庆宴会,要求与会职员的快乐指数最大。但是,没有职员愿和直接上司一起参加宴会。

输入格式
第一行一个整数N。(1≤N≤6000)
接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128≤Ri≤127)
接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。
最后一行输入0,0。

输出格式
第1行:输出最大的快乐指数。

样例
样例输入
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
样例输出
5

分析

建立一个二维DP,第一维是节点的编号(也是子树的根),第二维是这个根节点参加与否,比如dp[3][1]表示以3号节点为子树的根,当它参会时整棵子树的快乐指数和;dp[3][0]表示以3号节点为子树的根,当它不参会时整棵子树的快乐指数和;这样就可以满足最优子结构的性质了。
d p [ i ] [ 0 ] = m a x ( d p [ k ] [ 0 ] , d p [ k ] [ 1 ] ) dp[i][0]=max(dp[k][0],dp[k][1]) dp[i][0]=max(dp[k][0],dp[k][1])

d p [ i ] [ 1 ] = d p [ k ] [ 0 ] + w [ i ] dp[i][1]=dp[k][0]+w[i] dp[i][1]=dp[k][0]+w[i]

注意:本题输入的是一棵有根树(制定了节点间的上下关系),故我们需要先找出没有上司的节点root作为根,DP的目标答案在 m a x ( d p [ r o o t ] [ 1 ] , d p [ r o o t ] [ 0 ] ) max(dp[root][1], dp[root][0]) max(dp[root][1],dp[root][0])中。
时间复杂度为O(n)。

#include<cstdio> 
#include<algorithm>
#include<cstring>
#include<vector>
#define max(x,y) x>y?x:y
using namespace std;
const int M=1e5+5;
int a[M];
vector<int> G[M];
bool flag[M];
int dp[M][5];
void read(int &x) {
   
	x = 0; 
	int f = 1;
	char s = getchar(); 
	while (s > '9' || s < '0') {
    
		if (s == '-') f = -1; 
		s = getchar(); 
	}
	while (s >= '0' && s <= '9') {
    
		x = (x << 3) + (x << 1) + (s - '0');
		s = getchar(); 
	}
	x *= f;
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;