跟 Nachia DP 博客学DP-9 : 树形 DP

遇到关于有根树 $T$ 的问题时,设定以下子问题

Tip

$dp[v] =$ (关于顶点 $v$ 问题的解)

该问题通常涉及顶点 $v$ 的子树问题,这里顶点 $v$ 的子树是指顶点集合为 $T$ 中 $v$ 的后代,且根为 $v$ 的 $T$ 的有根子树

仅从叶子向根方向(自底向上)或从根向叶子方向(自顶向下)之一扫描有根树的 DP 被称为树形 DP

树形 DP 有非常非常多变种,该文章仅讨论最简单的一类,其余的会在后续文章出现

对于树形 DP 入门,我更倾向于在例题的辅助下讲解,其本身并不具有什么可以讲的东西(与正常的 动态规划 相区分)

讲解

例题1: AtCoder EDPC: P - Independent Set

AtCoder EDPC: P - Independent Set

Note

一颗有 $N$ 个节点的树,编号从 $1,2,\dots,N$ ,对于每个 $i = 1,2,\dots,N-1$,第 $i$ 条边连接 $x_i$ 与 $y_i$ 两个点. 太郎打算把每个点染成色,相邻两个点不能都是黑色的.

找到染色方案数,对 $10^9 + 7$ 取模.

[约束]

  • $1 \le N \le 10^5$
  • $1 \le x_i,y_i \le N$

这道题使用一个维度显然是不足以存储所有的元素的,因为该点是白还是黑色是影响其后续递推的,于是我们重新设 DP 表:$dp[v][color]=$ 以点 $v$ 为根且点 $v$ 是 $color$ 颜色时候的方案数,其中 $color = 0/1$

如果当前点是白色的话,其孩子可以是白或黑色的;如果当前点是黑色的话,其孩子仅可以是白色的,于是我们就可以写出来:

于是我们可以写出一个代码:

auto Mainsol = [](){
  INT(N);
  v<v<I>> e(N);
  rep(N - 1) {
  	INT(x, y); -- x, -- y;
  	e[x].pb(y); e[y].pb(x);
  }
  v<a<mm ,2>> f(N, {0, 0});
  auto dfs = [&](auto&&dfs, int u, int fa = -1) -> void {
  	f[u][0] = f[u][1] = 1;
  	each(v, e[u]) {
  		if(v == fa) continue;
  		dfs(dfs, v, u);
  		f[u][0] *= f[v][0] + f[v][1];
  		f[u][1] *= f[v][0];
  	}
  };
  dfs(dfs, 0);
  out((f[0][0] + f[0][1]).x);
};

相同的题: AtCoder ABC036: D - 涂色画

例题2: AtCoder TDPC: N - 木

AtCoder TDPC: N - 木

Note

给定你 $N - 1$ 条边,你可以以任意顺序选择边被绘画的先后,但是每一次绘画后,你要保证所有边是联通的,求出绘画方案,对 $10^9 + 7$ 取模

[约束]

  • $2 \le N \le 1000$
  • $1 \le a_i,b_i \le N$

如果我们假设第一条绘画的边上有一个点是整体的根,那么对于所有的子树而言,第一次遇到一个新节点时,绘画的边必须包含子树的根,这样才能满足联通,但是一旦联通后,后续的所有边的选择顺序无所谓

所以我们知道,当边 $e_v$ 是连接 $v$ 和其父亲的边,我们必须把 $e_v$ 放在所有其他 $v$ 的子树中的边之前,而放在前面的概率是 $P(e_v) = \frac{1}{size[v]}$

那么所有非根节点的情况都满足的概率就是 : $P = \prod_{v=2}^N \frac{1}{size[v]}$

$N - 1$ 条边的所有排列法就是 $(N - 1) !$,那么合法方案数就是 $P \cdot (N-1)! = \frac{(N-1)!}{\prod_{v=2}^N {size[v]}}$

于是我们先枚举以每个点为根的情况,在每个点为根的时候,预处理出其余点在当前情况下的子树大小即可,复杂度是 $O(N^2)$ 的

这道题的其实的动态规划其实在求子树大小!

显然有: $size[u] = 1 + \sum_{v \in to[u]} size[v]$

但是我们枚举每一个点之后会把相同的点计算两次贡献,所以答案要除以 $2$,这样就可以了,代码如下:

auto Mainsol = [](){
  INT(N); 
  Comb C(N * N); // 预处理阶乘
  v<v<I>> e(N);
  rep(N - 1) {
  	INT(x, y); -- x, -- y;
  	e[x].pb(y), e[y].pb(x);
  }
  mm ans = 0, cons = C.fac(N - 1);
  dbg(cons.x);
  rep(root, N) {
  	v<ll> f(N, 0); // size
	auto dfs = [&](auto&&dfs, int u, int fa = -1) -> void{
		f[u] = 1;
		each(v, e[u]) if(v != fa) {
			dfs(dfs, v, u);
			f[u] += f[v];
		}
	};
	dfs(dfs, root);
	mm now = cons;
	rep(i, N) if(i != root) now *= ((mm)f[i]).inv();
  	ans += now;
  }
  out((ans/2).x);
};

其他的树形 DP,请期待后续的文章,谢谢!

拓展阅读与例题

https://yukicoder.me/problems/no/417

http://tdpc.contest.atcoder.jp/tasks/tdpc_eel

http://codeforces.com/contest/855/problem/C

拓展阅读

树形 DP 教程 - Codeforces --- DP on Trees Tutorial - Codeforces

竞技编程中的树形 DP 问题总结 - 哈马扬哈马扬哈马扬 --- 競技プログラミングにおける木DP問題まとめ - はまやんはまやんはまやん