SasebonoShigure

\(\large\texttt{Warning:}\) 此篇博客中的代码是本人在 \(2019\) 年到 \(2021\) 年间断断续续写的,所以码风有较大的差异 ,后期会更改代码。

树形 \(DP\)

只要你学会了树,还学会了 \(dp\) ,那么你就学会了树形 \(dp\)\(By\) 某不愿透露姓名的教练

  • 什么是树形 \(DP\)

    树形 \(DP\),就是在“树”的数据结构上的动态规划,一般状态转移都是和子树相关,且能与线段树等数据结构相结合。

    因为其具有传递性,就对于具有一定规律的树上问题求解起到了很大帮助。

  • 常见的题型
    • 子树和计数:

      这类问题主要是统计子树和,通过加减一些子树满足题目中要求的某些性质

      例如 \(CF767C、Luogu\ P1122\)

    • 树上背包问题:

      这类问题就是让你求在树上选一些点满足价值最大的问题,一般都可以设 \(\large f_{i,j}\) 表示 \(i\) 这颗子树选 \(j\) 个点的最优解。

      例如 \(Luogu\ P1272\ P1273\)

    • 花费最少的费用覆盖所有点:

      这类问题是父亲与孩子有联系的题。基本有两种类型:

      • 选父亲必须不能选孩子(强制)

      • 选父亲可以不用选孩子(不强制)

      例如 \(UVA\ 1220\)(类型1)、\(Luogu\ P2458\)(类型2)

    • 树上统计方案数:

      这类问题就是给你一个条件,问你有多少个点的集合满足这样的条件。这类题主要运用乘法原理,控制一个点不动,看他能做多少贡献

    • 与多种算法结合

      这类问题就只能根据题目分析,听天由命了\(\cdots\cdots\)

  • 例题:
    • \(Luogu\ P2015\) 二叉苹果树:

      这道题属于常见题型中的树上背包问题,可以将其作为模板题。

      这道题还有一个隐含的条件,当某条边被保留下来时,从根节点到这条边的路径上的所有边也都必须保留下来。

      所以,我们可以很容易定义我们的 \(dp\) 状态。令 \(\large f_{i,j}\) 表示在 \(i\) 子树中保留 \(j\) 条边能够得到的最大苹果树。

      那么,状态转移方程就显而易见了:\(\large\mathcal{ f_{u,i}=\max(f_{u,i},f_{u,i-j-1}+f_{v,j}+Apple_{u,v})}\) ,其中 \(v\)\(u\) 的子节点,\(\mathcal{Apple_{u,v}}\) 表示 \(u \rightarrow v\) 这条边上的苹果数。

      注意: 由于这是一个 \(0/1\) 背包,所以 \(i、j\) 需要倒序遍历。

      代码:

      #include <cstdio>
      #include <vector> 
      #include <algorithm>
      
      using namespace std;
      
      const int MAXN = 110;
      
      typedef pair<int, int> T;
      
      vector <T> Tree[MAXN];
      
      int n, q, DP[MAXN][MAXN];
      bool Visited[MAXN];
      
      void DFS(int Father, int Node) {
      	for (int i = 0; i < Tree[Node].size(); i ++) {
      		T Son = Tree[Node][i];
      		if (Son.first != Father and Visited[Son.first] == false) {
      			DFS(Node, Son.first);
      			for (int j = q; j > 0; j --) {
      				for (int k = j - 1; k >= 0; k --) {
      					DP[Node][j] = max(DP[Node][j], Son.second + DP[Son.first][k] + DP[Node][j - k - 1]);
      				}
      			}
      		}
      	}
      	
      	return ;
      }
      
      int main () {
      	scanf ("%d %d", &n, &q);
      	
      	for (int i = 1; i < n; i ++) {
      		int u, v, w;
      		scanf ("%d %d %d", &u, &v, &w);
      		Tree[u].push_back(make_pair(v, w));
      		Tree[v].push_back(make_pair(u, w));
      	}
      	
      	DFS(1, 1);
      	
      	printf ("%d\n", DP[1][q]);
      	return 0;
      }
      

未完待续

相关文章: