目录

学习笔记-树形DP

思路:将一颗大树分为若干子树,枚举每个子树

  • 如何分割一棵树
  1. 利用中序遍历

    例如 P1040 [NOIP2003 提高组] 加分二叉树,利用中序遍历,所有的左儿子都在根节点的左边,所有的右儿子都在根节点的右边,且所有的子树在遍历中都是连续的。 因此可以枚举区间长度,再枚举起点和根节点。此外,应默认子树都没有左子树,否则一定不是最优解。

code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
using namespace std;
long long dp[1001][1001], root[1001][1001];
void print(long long l, long long r)
{
	if(l > r)
		return;
	cout << root[l][r] << " ";//print root
	if(l == r)
		return;
	print(l, root[l][r] - 1);
	print(root[l][r] + 1, r);
}
int main()
{
	long long n;
	cin >> n;
	for (long long i = 1;i <= n;i++)
	{
		cin >> dp[i][i];
		root[i][i] = i;
	}
	for(long long l = 1;l < n;l++)
	{
		for(long long i = 1;i + l <= n;i++)
		{
			long long j = i + l;
			dp[i][j] = dp[i + 1][j] + dp[i][i];//right subtree
			root[i][j] = i;
			for(long long k = 1 + i;k < j;k++)
			{
				if(dp[i][j] < dp[i][k - 1] * dp[k + 1][j] + dp[k][k])
				{
					dp[i][j] = dp[i][k - 1] * dp[k + 1][j] + dp[k][k];
					root[i][j] = k;
				}
			}
		}
	}
	cout << dp[1][n] << endl;
	print(1, n);
	cout << endl;
	return 0;
}
/*
   f[i][j]来表示节点i到节点j最大值
   */

  1. 递归dfs

    例子: P2015二叉苹果树 这道题需要用邻接表来存储每一条边。然后在dfs函数中只要一个节点有儿子(这条边所指向的点不是根节点),就以这个儿子为根节点再次进行dfs,并将其存储起来。因为每个节点都有且仅有两个分支,因此先遍历到的就是左儿子,后遍历到的是右儿子。

    code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for(int i = heads[x];i != 0;i = edge[i].next)
	{
		if(edge[i].to != fa)
		{
			doHaveSon = 1;
			s[++cnt] = i;
			dfs(edge[i].to, x);//subtree dfs
		}
	}


接下来进行dp, $dp[k,j]$表示以结点 $k$ 为父结点的子树中,保留j个树枝能留住的最多苹果数。所以转移方程为 $dp[i][j]=max(dp[i][j],dp[left][j−1]+edge[left].w+dp[right][k−j−1]+edge[right].w)$ (考虑left与i之间的树枝)

code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
for(int i = 1;i <= q;i++)
	{
		for(int j = 0;j <= i;j++)
		{
			int tot = 0;
			if(j - 1 >= 0)
				tot += edge[s[1]].w;//lc
			if(i - j - 1 >= 0)
				tot += edge[s[2]].w;//rc
			if(j != 0)
				dp[x][i] = max(dp[x][i], dp[edge[s[1]].to][j-1] + tot + dp[edge[s[2]].to][i - j - 1]);
			else
				dp[x][i] = max(dp[x][i], dp[edge[s[2]].to][i - j - 1] + tot);

		}
	}

总的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*************************************************************************
  > File Name: appletree.cpp
  > Created Time: 09/03/21 - 15:45
 ************************************************************************/

#include<bits/stdc++.h>
using namespace std;
struct EDGE{
	int w, to, next;
}edge[1001];
int n, q, tot, dp[101][101], heads[101];
void add_edge(int from, int to, int w)
{
	edge[++tot].next = heads[from];
	edge[tot].w = w;
	edge[tot].to = to;
	heads[from] = tot;
}
void dfs(int x, int fa);
int main()
{
	cin >> n >> q;
	for(int i = 1;i < n;i++)    
	{
		int a, b, c;
		cin >> a >> b >> c;
		add_edge(a, b, c);
		add_edge(b, a, c);
	}
	dfs(1, 0);
	cout << dp[1][q] << endl; 
	return 0;
}

void dfs(int x, int fa)
{
	int s[3] = {0}, cnt = 0;//s[1] left s[2] right
	bool doHaveSon = 0;
	for(int i = heads[x];i != 0;i = edge[i].next)
	{
		if(edge[i].to != fa)
		{
			doHaveSon = 1;
			s[++cnt] = i;
			dfs(edge[i].to, x);//subtree dfs
		}
	}
	if(!doHaveSon)
		return;
	for(int i = 1;i <= q;i++)
	{
		for(int j = 0;j <= i;j++)
		{
			int tot = 0;
			if(j - 1 >= 0)
				tot += edge[s[1]].w;//lc
			if(i - j - 1 >= 0)
				tot += edge[s[2]].w;//rc
			if(j != 0)
				dp[x][i] = max(dp[x][i], dp[edge[s[1]].to][j-1] + tot + dp[edge[s[2]].to][i - j - 1]);
			else
				dp[x][i] = max(dp[x][i], dp[edge[s[2]].to][i - j - 1] + tot);

		}
	}
}
————————分界线————————
  • 树形背包DP

    例子:P2014 选课

    没啥好说的

    code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*************************************************************************
	> File Name: choosecourse.cpp
	> Created Time: 10/03/21 - 09:51
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
vector<int> E[101];
int dp[1001][1001], n, m, w[1001];//i as root choose j item
void dfs(int x)
{
	for(int i : E[x])
	{
		dfs(i);
		for(int j = m;j >= 0; j--)//总体积
		{
			for(int k = 0;k <= j; k++)//枚举子树的大小
			{
				dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[i][k]);//i k子树选了k体积的价值 x j-k其他的选了剩下体积的价值
			}
		}
	}
	if(x)
	{
		for(int i = m;i > 0; i--)
		{
			dp[x][i] = dp[x][i - 1] + w[x];
		}
	}
}
int main()
{
	cin >> n >> m;
	for(int i = 1;i <= n; i++)
	{
		int fa;
		cin >> fa >> w[i];
		E[fa].push_back(i);
	}
	dfs(0);
	cout << dp[0][m] << endl;
	return 0;
}

一些有1点复杂的情况

例题:Vijos 1144 小胖守皇宫

HZOJ链接(反正Vijos在机房上不去:THIS

这道题分了三种情况, 在f数组中对应如下